diff --git a/docs/static/_redirects b/docs/static/_redirects
index 2114ae933f732..676181b61f938 100644
--- a/docs/static/_redirects
+++ b/docs/static/_redirects
@@ -10,3 +10,7 @@ https://gitea-docs.netlify.com/* https://docs.gitea.io/:splat 302!
 /en-us/ci-cd/ /en-us/integrations/ 302!
 /en-us/third-party-tools/ /en-us/integrations/ 302!
 /en-us/make/ /en-us/hacking-on-gitea/ 302!
+/en-us/upgrade/ /en-us/upgrade-from-gitea/ 302!
+/fr-fr/upgrade/ /fr-fr/upgrade-from-gitea/ 302!
+/zh-cn/upgrade/ /zh-cn/upgrade-from-gitea/ 302!
+/zh-tw/upgrade/ /zh-tw/upgrade-from-gitea/ 302!
diff --git a/docs/static/open-in-gitpod.svg b/docs/static/open-in-gitpod.svg
new file mode 100644
index 0000000000000..b97cd2948794d
--- /dev/null
+++ b/docs/static/open-in-gitpod.svg
@@ -0,0 +1,23 @@
+
+  
+     
+  
+    
+       
+    
+       
+   
+ 
\ No newline at end of file
diff --git a/go.mod b/go.mod
index e06ccfe13d84f..65a5cf7b40233 100644
--- a/go.mod
+++ b/go.mod
@@ -5,114 +5,121 @@ go 1.18
 require (
 	code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b
 	code.gitea.io/sdk/gitea v0.15.1
-	gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb
+	codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
+	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
-	gitea.com/lunny/levelqueue v0.4.1
+	gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
+	gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7
 	github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/PuerkitoBio/goquery v1.8.0
-	github.com/alecthomas/chroma v0.10.0
-	github.com/blevesearch/bleve/v2 v2.3.2
-	github.com/buildkite/terminal-to-html/v3 v3.6.1
-	github.com/caddyserver/certmagic v0.16.1
+	github.com/alecthomas/chroma/v2 v2.4.0
+	github.com/blevesearch/bleve/v2 v2.3.5
+	github.com/buildkite/terminal-to-html/v3 v3.7.0
+	github.com/caddyserver/certmagic v0.17.2
 	github.com/chi-middleware/proxy v1.1.1
-	github.com/denisenkom/go-mssqldb v0.12.0
+	github.com/denisenkom/go-mssqldb v0.12.2
 	github.com/djherbis/buffer v1.2.0
 	github.com/djherbis/nio/v3 v3.0.1
-	github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499
+	github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5
 	github.com/dustin/go-humanize v1.0.0
-	github.com/editorconfig/editorconfig-core-go/v2 v2.4.4
+	github.com/editorconfig/editorconfig-core-go/v2 v2.4.5
 	github.com/emirpasic/gods v1.18.1
 	github.com/ethantkoenig/rupture v1.0.1
-	github.com/felixge/fgprof v0.9.2
-	github.com/gliderlabs/ssh v0.3.4
+	github.com/felixge/fgprof v0.9.3
+	github.com/fsnotify/fsnotify v1.5.4
+	github.com/gliderlabs/ssh v0.3.5
+	github.com/go-ap/activitypub v0.0.0-20220917143152-e4e7018838c0
+	github.com/go-ap/jsonld v0.0.0-20220917142617-76bf51585778
 	github.com/go-chi/chi/v5 v5.0.7
 	github.com/go-chi/cors v1.2.1
-	github.com/go-enry/go-enry/v2 v2.8.2
-	github.com/go-fed/httpsig v1.1.0
+	github.com/go-enry/go-enry/v2 v2.8.3
+	github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
 	github.com/go-git/go-billy/v5 v5.3.1
-	github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4
-	github.com/go-ldap/ldap/v3 v3.4.3
+	github.com/go-git/go-git/v5 v5.4.3-0.20220529141257-bc1f419cebcf
+	github.com/go-ldap/ldap/v3 v3.4.4
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.6.0
-	github.com/go-swagger/go-swagger v0.29.0
-	github.com/go-testfixtures/testfixtures/v3 v3.6.1
+	github.com/go-swagger/go-swagger v0.30.3
+	github.com/go-testfixtures/testfixtures/v3 v3.8.1
 	github.com/gobwas/glob v0.2.3
 	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
 	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
 	github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
-	github.com/golang-jwt/jwt/v4 v4.4.1
-	github.com/google/go-github/v45 v45.0.0
-	github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3
+	github.com/golang-jwt/jwt/v4 v4.4.2
+	github.com/google/go-github/v45 v45.2.0
+	github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/feeds v1.1.1
 	github.com/gorilla/sessions v1.2.1
-	github.com/hashicorp/go-version v1.4.0
+	github.com/hashicorp/go-version v1.6.0
 	github.com/hashicorp/golang-lru v0.5.4
 	github.com/huandu/xstrings v1.3.2
 	github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba
 	github.com/json-iterator/go v1.1.12
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
 	github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4
-	github.com/klauspost/compress v1.15.3
-	github.com/klauspost/cpuid/v2 v2.0.12
-	github.com/lib/pq v1.10.5
-	github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
-	github.com/markbates/goth v1.72.0
-	github.com/mattn/go-isatty v0.0.14
-	github.com/mattn/go-sqlite3 v1.14.12
+	github.com/klauspost/compress v1.15.11
+	github.com/klauspost/cpuid/v2 v2.1.1
+	github.com/lib/pq v1.10.7
+	github.com/markbates/goth v1.73.0
+	github.com/mattn/go-isatty v0.0.16
+	github.com/mattn/go-sqlite3 v1.14.15
 	github.com/mholt/archiver/v3 v3.5.1
-	github.com/microcosm-cc/bluemonday v1.0.18
-	github.com/minio/minio-go/v7 v7.0.26
-	github.com/msteinert/pam v1.0.0
+	github.com/microcosm-cc/bluemonday v1.0.20
+	github.com/minio/minio-go/v7 v7.0.39
+	github.com/msteinert/pam v1.1.0
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
-	github.com/niklasfasching/go-org v1.6.2
+	github.com/niklasfasching/go-org v1.6.5
 	github.com/oliamb/cutter v0.2.2
 	github.com/olivere/elastic/v7 v7.0.32
 	github.com/pkg/errors v0.9.1
 	github.com/pquerna/otp v1.3.0
-	github.com/prometheus/client_golang v1.12.1
+	github.com/prometheus/client_golang v1.13.0
 	github.com/quasoft/websspi v1.1.2
-	github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
+	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/sergi/go-diff v1.2.0
 	github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
-	github.com/stretchr/testify v1.7.1
+	github.com/stretchr/testify v1.8.0
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/tstranex/u2f v1.0.0
-	github.com/unrolled/render v1.4.1
-	github.com/urfave/cli v1.22.9
-	github.com/xanzy/go-gitlab v0.64.0
+	github.com/unrolled/render v1.5.0
+	github.com/urfave/cli v1.22.10
+	github.com/xanzy/go-gitlab v0.73.1
 	github.com/yohcop/openid-go v1.0.0
-	github.com/yuin/goldmark v1.4.12
-	github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
+	github.com/yuin/goldmark v1.5.2
+	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
 	github.com/yuin/goldmark-meta v1.1.0
 	go.jolheiser.com/hcaptcha v0.0.4
 	go.jolheiser.com/pwn v0.0.3
-	golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
-	golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
-	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
-	golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
-	golang.org/x/text v0.3.7
-	golang.org/x/tools v0.1.10
+	golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
+	golang.org/x/net v0.2.0
+	golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
+	golang.org/x/sys v0.2.0
+	golang.org/x/text v0.4.0
+	golang.org/x/tools v0.1.12
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
-	gopkg.in/ini.v1 v1.66.4
+	gopkg.in/ini.v1 v1.67.0
 	gopkg.in/yaml.v2 v2.4.0
+	gopkg.in/yaml.v3 v3.0.1
 	mvdan.cc/xurls/v2 v2.4.0
 	strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
 	xorm.io/builder v0.3.11
-	xorm.io/xorm v1.3.1
+	xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f
 )
 
 require (
-	cloud.google.com/go v0.99.0 // indirect
-	github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect
-	github.com/Microsoft/go-winio v0.5.2 // indirect
-	github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
-	github.com/PuerkitoBio/purell v1.1.1 // indirect
-	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
-	github.com/RoaringBitmap/roaring v0.9.4 // indirect
+	cloud.google.com/go/compute v1.7.0 // indirect
+	git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
+	github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
+	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.1.1 // indirect
+	github.com/Masterminds/sprig/v3 v3.2.2 // indirect
+	github.com/Microsoft/go-winio v0.6.0 // indirect
+	github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad // indirect
+	github.com/RoaringBitmap/roaring v1.2.1 // indirect
 	github.com/acomagu/bufpipe v1.0.3 // indirect
 	github.com/andybalholm/brotli v1.0.4 // indirect
 	github.com/andybalholm/cascadia v1.3.1 // indirect
@@ -121,26 +128,28 @@ require (
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bgentry/speakeasy v0.1.0 // indirect
-	github.com/bits-and-blooms/bitset v1.2.2 // indirect
-	github.com/blevesearch/bleve_index_api v1.0.1 // indirect
+	github.com/bits-and-blooms/bitset v1.3.3 // indirect
+	github.com/blevesearch/bleve_index_api v1.0.4 // indirect
+	github.com/blevesearch/geo v0.1.15 // indirect
 	github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
 	github.com/blevesearch/gtreap v0.1.1 // indirect
-	github.com/blevesearch/mmap-go v1.0.3 // indirect
-	github.com/blevesearch/scorch_segment_api/v2 v2.1.0 // indirect
+	github.com/blevesearch/mmap-go v1.0.4 // indirect
+	github.com/blevesearch/scorch_segment_api/v2 v2.1.3 // indirect
 	github.com/blevesearch/segment v0.9.0 // indirect
 	github.com/blevesearch/snowballstem v0.9.0 // indirect
 	github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
-	github.com/blevesearch/vellum v1.0.7 // indirect
-	github.com/blevesearch/zapx/v11 v11.3.3 // indirect
-	github.com/blevesearch/zapx/v12 v12.3.3 // indirect
-	github.com/blevesearch/zapx/v13 v13.3.3 // indirect
-	github.com/blevesearch/zapx/v14 v14.3.3 // indirect
-	github.com/blevesearch/zapx/v15 v15.3.3 // indirect
+	github.com/blevesearch/vellum v1.0.9 // indirect
+	github.com/blevesearch/zapx/v11 v11.3.6 // indirect
+	github.com/blevesearch/zapx/v12 v12.3.6 // indirect
+	github.com/blevesearch/zapx/v13 v13.3.6 // indirect
+	github.com/blevesearch/zapx/v14 v14.3.6 // indirect
+	github.com/blevesearch/zapx/v15 v15.3.6 // indirect
 	github.com/boombuler/barcode v1.0.1 // indirect
 	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
 	github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/cloudflare/cfssl v1.6.1 // indirect
+	github.com/cloudflare/circl v1.2.0 // indirect
 	github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
 	github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
@@ -151,34 +160,34 @@ require (
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
-	github.com/dlclark/regexp2 v1.4.0 // indirect
+	github.com/dlclark/regexp2 v1.7.0 // indirect
 	github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
-	github.com/envoyproxy/go-control-plane v0.10.1 // indirect
+	github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
 	github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
-	github.com/felixge/httpsnoop v1.0.2 // indirect
+	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
-	github.com/fsnotify/fsnotify v1.5.4 // indirect
 	github.com/fullstorydev/grpcurl v1.8.1 // indirect
 	github.com/fxamacker/cbor/v2 v2.4.0 // indirect
+	github.com/go-ap/errors v0.0.0-20220917143055-4283ea5dae18 // indirect
 	github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
 	github.com/go-enry/go-oniguruma v1.2.1 // indirect
 	github.com/go-git/gcfg v1.5.0 // indirect
-	github.com/go-openapi/analysis v0.21.2 // indirect
-	github.com/go-openapi/errors v0.20.2 // indirect
+	github.com/go-openapi/analysis v0.21.4 // indirect
+	github.com/go-openapi/errors v0.20.3 // indirect
 	github.com/go-openapi/inflect v0.19.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
-	github.com/go-openapi/jsonreference v0.19.6 // indirect
-	github.com/go-openapi/loads v0.21.0 // indirect
-	github.com/go-openapi/runtime v0.21.1 // indirect
-	github.com/go-openapi/spec v0.20.4 // indirect
-	github.com/go-openapi/strfmt v0.21.1 // indirect
-	github.com/go-openapi/swag v0.19.15 // indirect
-	github.com/go-openapi/validate v0.20.3 // indirect
-	github.com/go-stack/stack v1.8.1 // indirect
-	github.com/goccy/go-json v0.9.7 // indirect
+	github.com/go-openapi/jsonreference v0.20.0 // indirect
+	github.com/go-openapi/loads v0.21.2 // indirect
+	github.com/go-openapi/runtime v0.24.1 // indirect
+	github.com/go-openapi/spec v0.20.7 // indirect
+	github.com/go-openapi/strfmt v0.21.3 // indirect
+	github.com/go-openapi/swag v0.22.3 // indirect
+	github.com/go-openapi/validate v0.22.0 // indirect
+	github.com/goccy/go-json v0.9.11 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
-	github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 // indirect
+	github.com/golang-sql/sqlexp v0.1.0 // indirect
+	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
@@ -197,7 +206,7 @@ require (
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
-	github.com/imdario/mergo v0.3.12 // indirect
+	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/jessevdk/go-flags v1.5.0 // indirect
@@ -209,17 +218,18 @@ require (
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/libdns/libdns v0.2.1 // indirect
-	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/markbates/going v1.0.0 // indirect
-	github.com/mattn/go-runewidth v0.0.13 // indirect
+	github.com/mattn/go-runewidth v0.0.14 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
-	github.com/mholt/acmez v1.0.2 // indirect
-	github.com/miekg/dns v1.1.48 // indirect
+	github.com/mholt/acmez v1.0.4 // indirect
+	github.com/miekg/dns v1.1.50 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/minio/sha256-simd v1.0.0 // indirect
-	github.com/mitchellh/go-homedir v1.1.0 // indirect
-	github.com/mitchellh/mapstructure v1.4.3 // indirect
+	github.com/mitchellh/copystructure v1.2.0 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
@@ -227,72 +237,73 @@ require (
 	github.com/nwaples/rardecode v1.1.3 // indirect
 	github.com/oklog/ulid v1.3.1 // indirect
 	github.com/olekukonko/tablewriter v0.0.5 // indirect
-	github.com/pelletier/go-toml v1.9.4 // indirect
-	github.com/pierrec/lz4/v4 v4.1.14 // indirect
+	github.com/pelletier/go-toml v1.9.5 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.1 // indirect
+	github.com/pierrec/lz4/v4 v4.1.17 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
-	github.com/prometheus/common v0.32.1 // indirect
-	github.com/prometheus/procfs v0.7.3 // indirect
-	github.com/rivo/uniseg v0.2.0 // indirect
-	github.com/rogpeppe/go-internal v1.8.1 // indirect
+	github.com/prometheus/common v0.37.0 // indirect
+	github.com/prometheus/procfs v0.8.0 // indirect
+	github.com/rivo/uniseg v0.4.2 // indirect
+	github.com/rogpeppe/go-internal v1.9.0 // indirect
 	github.com/rs/xid v1.4.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/shopspring/decimal v1.2.0 // indirect
 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/soheilhy/cmux v0.1.5 // indirect
-	github.com/spf13/afero v1.8.0 // indirect
-	github.com/spf13/cast v1.4.1 // indirect
-	github.com/spf13/cobra v1.3.0 // indirect
+	github.com/spf13/afero v1.8.2 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
+	github.com/spf13/cobra v1.5.0 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/spf13/viper v1.10.1 // indirect
+	github.com/spf13/viper v1.12.0 // indirect
 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
-	github.com/subosito/gotenv v1.2.0 // indirect
+	github.com/subosito/gotenv v1.3.0 // indirect
 	github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
 	github.com/toqueteos/webbrowser v1.2.0 // indirect
 	github.com/ulikunitz/xz v0.5.10 // indirect
 	github.com/unknwon/com v1.0.1 // indirect
+	github.com/valyala/fastjson v1.6.3 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	github.com/xanzy/ssh-agent v0.3.1 // indirect
+	github.com/xanzy/ssh-agent v0.3.2 // indirect
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 	github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
-	go.etcd.io/etcd/api/v3 v3.5.1 // indirect
-	go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
-	go.etcd.io/etcd/client/v2 v2.305.1 // indirect
-	go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 // indirect
+	go.etcd.io/etcd/api/v3 v3.5.4 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
+	go.etcd.io/etcd/client/v2 v2.305.4 // indirect
+	go.etcd.io/etcd/client/v3 v3.5.4 // indirect
 	go.etcd.io/etcd/etcdctl/v3 v3.5.0-alpha.0 // indirect
 	go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 // indirect
 	go.etcd.io/etcd/raft/v3 v3.5.0-alpha.0 // indirect
 	go.etcd.io/etcd/server/v3 v3.5.0-alpha.0 // indirect
 	go.etcd.io/etcd/tests/v3 v3.5.0-alpha.0 // indirect
 	go.etcd.io/etcd/v3 v3.5.0-alpha.0 // indirect
-	go.mongodb.org/mongo-driver v1.8.2 // indirect
-	go.uber.org/atomic v1.9.0 // indirect
+	go.mongodb.org/mongo-driver v1.10.1 // indirect
+	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
-	go.uber.org/zap v1.21.0 // indirect
-	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
-	golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
-	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
+	go.uber.org/zap v1.23.0 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+	golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
-	google.golang.org/grpc v1.43.0 // indirect
-	google.golang.org/protobuf v1.28.0 // indirect
+	google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect
+	google.golang.org/grpc v1.47.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 	sigs.k8s.io/yaml v1.2.0 // indirect
 )
 
 replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
 
-replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce
-
 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
 
 replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible
 
+replace github.com/blevesearch/zapx/v15 v15.3.6 => github.com/zeripath/zapx/v15 v15.3.6-alignment-fix
+
 exclude github.com/gofrs/uuid v3.2.0+incompatible
 
 exclude github.com/gofrs/uuid v4.0.0+incompatible
diff --git a/go.sum b/go.sum
index 9c99adfbe14cc..38d8afa9e70bb 100644
--- a/go.sum
+++ b/go.sum
@@ -32,19 +32,26 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
 cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
 cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
-cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
-cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -56,12 +63,15 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
 code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
 code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b h1:uv9a8eGSdQ8Dr4HyUcuHFfDsk/QuwO+wf+Y99RYdxY0=
 code.gitea.io/gitea-vet v0.2.2-0.20220122151748-48ebc902541b/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
 code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
 code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
 code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
+codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
+codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
 contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
@@ -69,8 +79,10 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.5/go.mod h1:aXENhDJ1Y4lIg4EU
 contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE=
 contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-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=
+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-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=
@@ -78,8 +90,10 @@ gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5 h1:J/1i8u40TbcLP/w2w
 gitea.com/go-chi/captcha v0.0.0-20211013065431-70641c1a35d5/go.mod h1:hQ9SYHKdOX968wJglb/NMQ+UqpOKwW4L+EYdvkWjHSo=
 gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8 h1:tJQRXgZigkLeeW9LPlps9G9aMoE6LAmqigLA+wxmd1Q=
 gitea.com/go-chi/session v0.0.0-20211218221615-e3605d8b28b8/go.mod h1:fc/pjt5EqNKgqQXYzcas1Z5L5whkZHyOvTA7OzWVJck=
-gitea.com/lunny/levelqueue v0.4.1 h1:RZ+AFx5gBsZuyqCvofhAkPQ9uaVDPJnsULoJZIYaJNw=
-gitea.com/lunny/levelqueue v0.4.1/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
+gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:+wWBi6Qfruqu7xJgjOIrKVQGiLUZdpKYCZewJ4clqhw=
+gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:VyMQP6ue6MKHM8UsOXfNfuMKD0oSAWZdXVcpHIN2yaY=
+gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 h1:Zc3RQWC2xOVglLciQH/ZIC5IqSk3Jn96LflGQLv18Rg=
+gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
 gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
@@ -97,64 +111,66 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2
 github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
 github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
 github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ=
-github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
+github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
+github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
 github.com/GeertJohan/go.rice v1.0.2/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4=
 github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
 github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74=
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
+github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
 github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
+github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
+github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU=
-github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
+github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad h1:QeeqI2zxxgZVe11UrYFXXx6gVxPVF40ygekjBzEg4XY=
+github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
 github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
 github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
-github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
-github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
-github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
+github.com/RoaringBitmap/roaring v1.2.1 h1:58/LJlg/81wfEHd5L9qsHduznOIhyv4qb1yWcSvVq9A=
+github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
 github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
 github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
-github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
 github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
-github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
-github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
+github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
+github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
+github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
+github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ=
 github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
@@ -175,16 +191,10 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
-github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
-github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
-github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
 github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@@ -196,7 +206,6 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
 github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -211,65 +220,65 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE=
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
-github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk=
-github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
+github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg=
+github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM=
-github.com/blevesearch/bleve/v2 v2.3.2 h1:BJUnMhi2nrkl+vboHmKfW+9l+tJSj39HeWa5c3BN3/Y=
-github.com/blevesearch/bleve/v2 v2.3.2/go.mod h1:96+xE5pZUOsr3Y4vHzV1cBC837xZCpwLlX0hrrxnvIg=
+github.com/blevesearch/bleve/v2 v2.3.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8=
+github.com/blevesearch/bleve/v2 v2.3.5/go.mod h1:FneKGHMRrCLrp4X9+iy3wlBqgM2ALucg7bp8jUuAi/s=
 github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
-github.com/blevesearch/bleve_index_api v1.0.1 h1:nx9++0hnyiGOHJwQQYfsUGzpRdEVE5LsylmmngQvaFk=
-github.com/blevesearch/bleve_index_api v1.0.1/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
-github.com/blevesearch/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
+github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
+github.com/blevesearch/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU=
+github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
+github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4=
+github.com/blevesearch/geo v0.1.15/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
 github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
 github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
-github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
 github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
 github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
 github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
-github.com/blevesearch/mmap-go v1.0.3 h1:7QkALgFNooSq3a46AE+pWeKASAZc9SiNFJhDGF1NDx4=
-github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
+github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
+github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
 github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU=
-github.com/blevesearch/scorch_segment_api/v2 v2.1.0 h1:NFwteOpZEvJk5Vg0H6gD0hxupsG3JYocE4DBvsA2GZI=
-github.com/blevesearch/scorch_segment_api/v2 v2.1.0/go.mod h1:uch7xyyO/Alxkuxa+CGs79vw0QY8BENSBjg6Mw5L5DE=
+github.com/blevesearch/scorch_segment_api/v2 v2.1.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ=
+github.com/blevesearch/scorch_segment_api/v2 v2.1.3/go.mod h1:eZrfp1y+lUh+DzFjUcTBUSnKGuunyFIpBIvqYVzJfvc=
 github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
 github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
-github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
 github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
 github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
 github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
 github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
 github.com/blevesearch/vellum v1.0.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo=
 github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRvNBlNMrEVgY=
-github.com/blevesearch/vellum v1.0.7 h1:+vn8rfyCRHxKVRgDLeR0FAXej2+6mEb5Q15aQE/XESQ=
-github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
+github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
+github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
 github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM=
-github.com/blevesearch/zapx/v11 v11.3.3 h1:8vQMO5hdA2qPCmicIMuKS+qcvUAEh6Vcb0uve4Nh8e4=
-github.com/blevesearch/zapx/v11 v11.3.3/go.mod h1:YzTfUm4kS3e8OmTXDHVV8OzC5MWPO/VPJZQgPNVb4Lc=
+github.com/blevesearch/zapx/v11 v11.3.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc=
+github.com/blevesearch/zapx/v11 v11.3.6/go.mod h1:B0CzJRj/pS7hJIroflRtFsa9mRHpMSucSgre0FVINns=
 github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A=
-github.com/blevesearch/zapx/v12 v12.3.3 h1:MQO5YNI8MqdPz12ALCoXiJw5cl9QQamYZSp285Z/+Mo=
-github.com/blevesearch/zapx/v12 v12.3.3/go.mod h1:RMl6lOZqF+sTxKvhQDJ5yK2LT3Mu7E2p/jGdjAaiRxs=
+github.com/blevesearch/zapx/v12 v12.3.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4=
+github.com/blevesearch/zapx/v12 v12.3.6/go.mod h1:iYi7tIKpauwU5os5wTxJITixr5Km21Hl365otMwdaP0=
 github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ=
-github.com/blevesearch/zapx/v13 v13.3.3 h1:TS4xpMK1ARPYHq+1WwuEOKMOiwvKpTK3RuWOkKlI7BE=
-github.com/blevesearch/zapx/v13 v13.3.3/go.mod h1:eppobNM35U4C22yDvTuxV9xPqo10pwfP/jugL4INWG4=
+github.com/blevesearch/zapx/v13 v13.3.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig=
+github.com/blevesearch/zapx/v13 v13.3.6/go.mod h1:X+FsTwCU8qOHtK0d/ArvbOH7qiIgViSQ1GQvcR6LSkI=
 github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g=
-github.com/blevesearch/zapx/v14 v14.3.3 h1:dqqAzGphKl0yehHKKntDHKlEMhi9B/tJrD4OsWpY7YE=
-github.com/blevesearch/zapx/v14 v14.3.3/go.mod h1:zXNcVzukh0AvG57oUtT1T0ndi09H0kELNaNmekEy0jw=
+github.com/blevesearch/zapx/v14 v14.3.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE=
+github.com/blevesearch/zapx/v14 v14.3.6/go.mod h1:9X8W3XoikagU0rwcTqwZho7p9cC7m7zhPZO94S4wUvM=
 github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A=
-github.com/blevesearch/zapx/v15 v15.3.3 h1:60oE+qsJkveLenJmbc0eaH59GWYCbJJsPDV6Z5hEoYY=
-github.com/blevesearch/zapx/v15 v15.3.3/go.mod h1:C+f/97ZzTzK6vt/7sVlZdzZxKu+5+j4SrGCvr9dJzaY=
 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
 github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/buildkite/terminal-to-html/v3 v3.6.1 h1:yHS+GXsPDXevb67YXjkVwZ4tolDCgPYa9RVOrzHlgGE=
-github.com/buildkite/terminal-to-html/v3 v3.6.1/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo=
+github.com/buildkite/terminal-to-html/v3 v3.7.0 h1:chdLUSpiOj/A4v3dzxyOqixXI6aw7IDA6Dk77FXsvNU=
+github.com/buildkite/terminal-to-html/v3 v3.7.0/go.mod h1:g0ME1XqbkBSgXR9YmlIHcJIjzaMyWW+HbsG0rPb5puo=
+github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
-github.com/caddyserver/certmagic v0.16.1 h1:rdSnjcUVJojmL4M0efJ+yHXErrrijS4YYg3FuwRdJkI=
-github.com/caddyserver/certmagic v0.16.1/go.mod h1:jKQ5n+ViHAr6DbPwEGLTSM2vDwTO6EvCKBblBRUvvuQ=
+github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
+github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
 github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
@@ -290,13 +299,14 @@ github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdi
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
-github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
 github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
 github.com/cloudflare/cfssl v1.6.1 h1:aIOUjpeuDJOpWjVJFP2ByplF53OgqG8I1S40Ggdlk3g=
 github.com/cloudflare/cfssl v1.6.1/go.mod h1:ENhCj4Z17+bY2XikpxVmTHDg/C2IsG2Q0ZBeXpAqhCk=
+github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
+github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
 github.com/cloudflare/redoctober v0.0.0-20201013214028-99c99a8e7544/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -346,11 +356,9 @@ github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFl
 github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401 h1:4KDlx3vjalrHD/EfsjCpV91HNX3JPaIqRtt83zZ7x+Y=
 github.com/couchbase/goutils v0.0.0-20210118111533-e33d3ffb5401/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
-github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
@@ -366,8 +374,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
 github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
-github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA=
-github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
+github.com/denisenkom/go-mssqldb v0.12.2 h1:1OcPn5GBIobjWNd+8yjfHNIaFX14B1pWI3F9HZy5KXw=
+github.com/denisenkom/go-mssqldb v0.12.2/go.mod h1:lnIw1mZukFRZDJYQ0Pb833QS2IaC3l5HkEfra2LJ+sk=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -379,16 +387,16 @@ github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ
 github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE=
 github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4=
 github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg=
-github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
-github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
-github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499 h1:jaQHuGKk9NVcfu9VbA7ygslr/7utxdYs47i4osBhZP8=
-github.com/duo-labs/webauthn v0.0.0-20220330035159-03696f3d4499/go.mod h1:UMk1JMDgQDcdI2vQz+WJOIUTSjIq07qSepAVgc93rUc=
+github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5 h1:BaeJtFDlto/NjX9t730OebRRJf2P+t9YEDz3ur18824=
+github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5/go.mod h1:Jcj7rFNlTknb18v9jpSA58BveX2LDhXqaoy+6YV1N9g=
 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -396,8 +404,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ
 github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/editorconfig/editorconfig-core-go/v2 v2.4.4 h1:FclmQqjEE8TqTFe6OGhaZY7MmbWVp9dBvv9vZkeC4Hk=
-github.com/editorconfig/editorconfig-core-go/v2 v2.4.4/go.mod h1:mz3wjBRdp75EfR6lp9qLmqyKp76AlT9I2Z13nALZeQs=
+github.com/editorconfig/editorconfig-core-go/v2 v2.4.5 h1:kTcVMyCvFGQmTk0S5+R7GF+y7wMHkWm4CYS5BwYWN8U=
+github.com/editorconfig/editorconfig-core-go/v2 v2.4.5/go.mod h1:rDB5UUleQsOI1HLbojaBmDNR8oUUe31InmNDTVzcDHY=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
@@ -412,8 +420,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
-github.com/envoyproxy/go-control-plane v0.10.1 h1:cgDRLG7bs59Zd+apAWuzLQL95obVYAymNJek76W3mgw=
-github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.6.1/go.mod h1:txg5va2Qkip90uYoSKH+nkAAmXrb2j3iq4FLwdrCbXQ=
@@ -425,12 +433,11 @@ github.com/ethantkoenig/rupture v1.0.1/go.mod h1:Sjqo/nbffZp1pVVXNGhpugIjsWmuS9K
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
-github.com/felixge/fgprof v0.9.2 h1:tAMHtWMyl6E0BimjVbFt7fieU6FpjttsZN7j0wT5blc=
-github.com/felixge/fgprof v0.9.2/go.mod h1:+VNi+ZXtHIQ6wIw6bUT8nXQRefQflWECoFyRealT5sg=
+github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
+github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
-github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
+github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
@@ -440,9 +447,9 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
 github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
 github.com/fullstorydev/grpcurl v1.8.0/go.mod h1:Mn2jWbdMrQGJQ8UD62uNyMumT2acsZUCkZIqFxsQf1o=
@@ -454,12 +461,16 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY
 github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
-github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
-github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
+github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
 github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
 github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/go-ap/activitypub v0.0.0-20220917143152-e4e7018838c0 h1:EUMB0x7u3de/ikGBtXiLxaJbmxgiqiAcM4yjW4whApM=
+github.com/go-ap/activitypub v0.0.0-20220917143152-e4e7018838c0/go.mod h1:OX9ajs2vU4UauC/DlghS/8M468Kn79r+y9kB6j7LuGM=
+github.com/go-ap/errors v0.0.0-20220917143055-4283ea5dae18 h1:A48SbkWKEciiJMbbcPzaRj9aizPUABzXFvCM3LtGGf8=
+github.com/go-ap/errors v0.0.0-20220917143055-4283ea5dae18/go.mod h1:dd3ZgjjloBsKPDpqA2kf2VWhF0A1eKUItOBh0/QcDWI=
+github.com/go-ap/jsonld v0.0.0-20220917142617-76bf51585778 h1:0tV3i8tE1NghMC4rXZXfD39KUbkKgIyLTsvOEmMOPCQ=
+github.com/go-ap/jsonld v0.0.0-20220917142617-76bf51585778/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
 github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
 github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@@ -468,21 +479,20 @@ github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
 github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
 github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
-github.com/go-enry/go-enry/v2 v2.8.2 h1:uiGmC+3K8sVd/6DOe2AOJEOihJdqda83nPyJNtMR8RI=
-github.com/go-enry/go-enry/v2 v2.8.2/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
+github.com/go-enry/go-enry/v2 v2.8.3 h1:BwvNrN58JqBJhyyVdZSl5QD3xoxEEGYUrRyPh31FGhw=
+github.com/go-enry/go-enry/v2 v2.8.3/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
 github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
 github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
-github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
-github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
+github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
+github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
-github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
 github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
 github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
-github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
-github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4 h1:1RSUwVK7VjTeA82kcLIqz1EU70QRwFdZUlJW58gP4GY=
-github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
+github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
+github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
+github.com/go-git/go-git/v5 v5.4.3-0.20220529141257-bc1f419cebcf h1:tmTaR5nN6RJs6G9+tmd3MRBNXSk6YTI9+8nv1WrIKzI=
+github.com/go-git/go-git/v5 v5.4.3-0.20220529141257-bc1f419cebcf/go.mod h1:cGzI9Lmtnh5wmJ5NjO/Cr3d6Iljyy588mEFUKs5vMvw=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -491,114 +501,51 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-ldap/ldap/v3 v3.4.3 h1:JCKUtJPIcyOuG7ctGabLKMgIlKnGumD/iGjuWeEruDI=
-github.com/go-ldap/ldap/v3 v3.4.3/go.mod h1:7LdHfVt6iIOESVEe3Bs4Jp2sHEKgDeduAhgM1/f9qmo=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs=
+github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
-github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
-github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
-github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
-github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
-github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
-github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
-github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk=
-github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
-github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og=
-github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
-github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
-github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
-github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
-github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
-github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
+github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
 github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
 github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
-github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
 github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc=
+github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
 github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
 github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
-github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
-github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
-github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
-github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
-github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
-github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
-github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
 github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
-github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
-github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
-github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
-github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
-github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
-github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY=
-github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
-github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc=
-github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4=
-github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o=
-github.com/go-openapi/loads v0.21.0 h1:jYtUO4wwP7psAweisP/MDoOpdzsYEESdoPcsWjHDR68=
-github.com/go-openapi/loads v0.21.0/go.mod h1:rHYve9nZrQ4CJhyeIIFJINGCg1tQpx2yJrrNo8sf1ws=
-github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
-github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
-github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
-github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
-github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
-github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
-github.com/go-openapi/runtime v0.21.1 h1:/KIG00BzA2x2HRStX2tnhbqbQdPcFlkgsYCiNY20FZs=
-github.com/go-openapi/runtime v0.21.1/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs=
-github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
-github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
-github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
-github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
-github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
-github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
-github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
-github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ=
-github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
-github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
+github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
+github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
+github.com/go-openapi/runtime v0.24.1 h1:Sml5cgQKGYQHF+M7yYSHaH1eOjvTykrddTE/KtQVjqo=
+github.com/go-openapi/runtime v0.24.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk=
 github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
-github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
-github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
-github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
-github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
-github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
-github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
-github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
-github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
-github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc=
-github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk=
+github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
+github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
+github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
 github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
-github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM=
 github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
-github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
-github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
-github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
+github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
+github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
-github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
-github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
-github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
 github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
-github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
-github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
-github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
-github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4=
-github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI=
-github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0=
-github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw=
-github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZXeftD16byIjA4=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
+github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y=
+github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
 github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
@@ -610,13 +557,12 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
 github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
-github.com/go-swagger/go-swagger v0.29.0 h1:z3YoZtLvS1Y8TE/PCat1VypcZxM0IgKLt0NvZxQyNl8=
-github.com/go-swagger/go-swagger v0.29.0/go.mod h1:Z4GJzI+bHKKkGB2Ji1rawpi3/ldXX8CkzGIa9HAC5EE=
+github.com/go-swagger/go-swagger v0.30.3 h1:HuzvdMRed/9Q8vmzVcfNBQByZVtT79DNZxZ18OprdoI=
+github.com/go-swagger/go-swagger v0.30.3/go.mod h1:neDPes8r8PCz2JPvHRDj8BTULLh4VJUt7n6MpQqxhHM=
 github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
-github.com/go-testfixtures/testfixtures/v3 v3.6.1 h1:n4Fv95Exp0D05G6l6CAZv22Ck1EJK0pa0TfPqE4ncSs=
-github.com/go-testfixtures/testfixtures/v3 v3.6.1/go.mod h1:Bsb2MoHAfHnNsPpSwAjtOs102mqDuM+1u3nE2OCi0N0=
+github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU=
+github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA=
 github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
 github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
 github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
@@ -646,8 +592,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
 github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
-github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -665,15 +611,16 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ
 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
-github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
-github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
-github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
-github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188 h1:+eHOFJl1BaXrQxKX+T06f78590z4qA2ZzBTqahsKSE4=
-github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
+github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
+github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
+github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -739,9 +686,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
-github.com/google/go-github/v45 v45.0.0 h1:LU0WBjYidxIVyx7PZeWb+FP4JZJ3Wh3FQgdumnGqiLs=
-github.com/google/go-github/v45 v45.0.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
+github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI=
+github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
 github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -749,6 +697,7 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
 github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
 github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/licenseclassifier v0.0.0-20210325184830-bb04aff29e72/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -772,8 +721,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
-github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3 h1:vFrXU7L2gqtlP/ZGijSpaDIc16ZQrZI4FAuYtpQTyQc=
-github.com/google/pprof v0.0.0-20220509035851-59ca7ad80af3/go.mod h1:Pt31oes+eGImORns3McJn8zHefuQl2rG8l6xQjGYB4U=
+github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40 h1:ykKxL12NZd3JmWZnyqarJGsF73M9Xhtrik/FEtEeFRE=
+github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
@@ -787,11 +736,16 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
 github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -840,30 +794,21 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
-github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
 github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
 github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
-github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo=
-github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
 github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
 github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
 github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
 github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -877,17 +822,13 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
-github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
-github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
-github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
+github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
 github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
+github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
 github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
@@ -899,8 +840,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:
 github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
-github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
@@ -917,8 +860,8 @@ github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr
 github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
-github.com/jackc/pgconn v1.9.0 h1:gqibKSTJup/ahCsNKyMZAniPuZEfIqfXFc8FOWVYR+Q=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
 github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
 github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@@ -933,8 +876,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
 github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
 github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
 github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
@@ -942,24 +885,20 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
 github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
 github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
-github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik=
 github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
 github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
 github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
-github.com/jackc/pgtype v1.8.0 h1:iFVCcVhYlw0PulYCVoguRGm0SE9guIcPcccnLzHj8bA=
 github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
-github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
-github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
+github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
 github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
 github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
 github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
-github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=
 github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
 github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
 github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
-github.com/jackc/pgx/v4 v4.12.0 h1:xiP3TdnkwyslWNp77yE5XAPfxAsU9RMFDe0c1SwN8h4=
 github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
+github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
@@ -979,12 +918,10 @@ github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4
 github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
 github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
 github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
-github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
@@ -992,6 +929,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -1025,16 +963,15 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
 github.com/kisom/goutils v1.4.3/go.mod h1:Lp5qrquG7yhYnWzZCI/68Pa/GpFynw//od6EkGnWpac=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.15.3 h1:wmfu2iqj9q22SyMINp1uQ8C2/V4M1phJdmH9fG4nba0=
-github.com/klauspost/compress v1.15.3/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
+github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
-github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
+github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
+github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
 github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
@@ -1044,12 +981,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -1070,14 +1005,12 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
-github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
 github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
 github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
-github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
 github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
 github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
 github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI=
@@ -1087,20 +1020,17 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
-github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
 github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
-github.com/markbates/goth v1.72.0 h1:Vm9OE+GsB7FrrvBqKEYsRBiPg4LWJ6DT5zD0XN2Rl4U=
-github.com/markbates/goth v1.72.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
+github.com/markbates/goth v1.73.0 h1:X5QUUHLP5puJ4dhoPKkV3PhDIvvQEzsfVxsUmDNSJ28=
+github.com/markbates/goth v1.73.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
@@ -1110,9 +1040,7 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
-github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -1120,66 +1048,64 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
-github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
+github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
-github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
+github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/mholt/acmez v1.0.2 h1:C8wsEBIUVi6e0DYoxqCcFuXtwc4AWXL/jgcDjF7mjVo=
-github.com/mholt/acmez v1.0.2/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
+github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
+github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
 github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
 github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
-github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
-github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
+github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
+github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
-github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
-github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
-github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
+github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
 github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.26 h1:D0HK+8793etZfRY/vHhDmFaP+vmT41K3K4JV9vmZCBQ=
-github.com/minio/minio-go/v7 v7.0.26/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg=
+github.com/minio/minio-go/v7 v7.0.39 h1:upnbu1jCGOqEvrGSpRauSN9ZG7RCHK7VHxXS8Vmg2zk=
+github.com/minio/minio-go/v7 v7.0.39/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
 github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
 github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1197,8 +1123,8 @@ github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkF
 github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
 github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
-github.com/msteinert/pam v1.0.0 h1:4XoXKtMCH3+e6GIkW41uxm6B37eYqci/DH3gzSq7ocg=
-github.com/msteinert/pam v1.0.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI=
+github.com/msteinert/pam v1.1.0 h1:VhLun/0n0kQYxiRBJJvVpC2jR6d21SWJFjpvUVj20Kc=
+github.com/msteinert/pam v1.1.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo=
@@ -1213,8 +1139,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/niklasfasching/go-org v1.6.2 h1:kQBIZlfL4oRNApJCrBgaeNBfzxWzP6XlC7/b744Polk=
-github.com/niklasfasching/go-org v1.6.2/go.mod h1:wn76Xgu4/KRe43WZhsgZjxYMaloSrl3BSweGV74SwHs=
+github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexphk8PGbo=
+github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM=
 github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
 github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
 github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
@@ -1264,22 +1190,22 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
 github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
-github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
+github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
-github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
-github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
+github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -1293,7 +1219,6 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
 github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
 github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -1301,13 +1226,14 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
-github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
+github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -1325,8 +1251,9 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
 github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.24.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -1335,8 +1262,9 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/pseudomuto/protoc-gen-doc v1.4.1/go.mod h1:exDTOVwqpp30eV/EDPFLZy3Pwr2sn6hBC1WIYH/UbIg=
 github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q=
@@ -1347,8 +1275,9 @@ github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqn
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
+github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@@ -1357,8 +1286,9 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
 github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
@@ -1370,10 +1300,9 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
-github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
-github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
+github.com/santhosh-tekuri/jsonschema/v5 v5.0.1 h1:HNLA3HtUIROrQwG1cuu5EYuqk3UEoJ61Dr/9xkd6sok=
+github.com/santhosh-tekuri/jsonschema/v5 v5.0.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
 github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -1382,6 +1311,7 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
@@ -1397,8 +1327,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@@ -1419,18 +1350,19 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
-github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
-github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
+github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
-github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
-github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
+github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -1441,9 +1373,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
-github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
-github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
-github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
+github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ=
+github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI=
 github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
@@ -1455,6 +1386,7 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -1462,10 +1394,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
+github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
@@ -1485,7 +1420,6 @@ github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9r
 github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
 github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
-github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
@@ -1497,35 +1431,36 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o
 github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
 github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
 github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/unrolled/render v1.4.1 h1:VdpMc2YkAOWzbmC/P2yoHhRDXgsaCQHcTJ1KK6SNCA4=
-github.com/unrolled/render v1.4.1/go.mod h1:cK4RSTTVdND5j9EYEc0LAMOvdG11JeiKjyjfyZRvV2w=
+github.com/unrolled/render v1.5.0 h1:uNTHMvVoI9pyyXfgoDHHycIqFONNY2p4eQR9ty+NsxM=
+github.com/unrolled/render v1.5.0/go.mod h1:eLTosBkQqEPEk7pRfkCRApXd++lm++nCsVlFOHpeedw=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
-github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
+github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
+github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
 github.com/weppos/publicsuffix-go v0.13.1-0.20210123135404-5fd73613514e/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
 github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
 github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
-github.com/xanzy/go-gitlab v0.64.0 h1:rMgQdW9S1w3qvNAH2LYpFd2xh7KNLk+JWJd7sorNuTc=
-github.com/xanzy/go-gitlab v0.64.0/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM=
+github.com/xanzy/go-gitlab v0.73.1 h1:UMagqUZLJdjss1SovIC+kJCH4k2AZWXl58gJd38Y/hI=
+github.com/xanzy/go-gitlab v0.73.1/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
-github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
-github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
 github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
+github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM=
+github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
-github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
@@ -1539,14 +1474,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
-github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
-github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg=
-github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU=
+github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
+github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
 github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
 github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+github.com/zeripath/zapx/v15 v15.3.6-alignment-fix h1:fKZ9OxEDoJKgM0KBXRbSb5IgKUEXis6C3zEIiMtzzQ0=
+github.com/zeripath/zapx/v15 v15.3.6-alignment-fix/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
 github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
@@ -1560,15 +1497,16 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
 go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw=
-go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
-go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
-go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
+go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
+go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
+go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.0-alpha.0/go.mod h1:kdV+xzCJ3luEBSIeQyB/OEKkWKd8Zkux4sbDeANrosU=
-go.etcd.io/etcd/client/v2 v2.305.1 h1:vtxYCKWA9x31w0WJj7DdqsHFNjhkigdAnziDtkZb/l4=
-go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
-go.etcd.io/etcd/client/v3 v3.5.0-alpha.0 h1:dr1EOILak2pu4Nf5XbRIOCNIBjcz6UmkQd7hHRXwxaM=
+go.etcd.io/etcd/client/v2 v2.305.4 h1:Dcx3/MYyfKcPNLpR4VVQUP5KgYrBeJtktBwEKkw08Ao=
+go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
 go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8=
+go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
+go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.etcd.io/etcd/etcdctl/v3 v3.5.0-alpha.0 h1:odMFuQQCg0UmPd7Cyw6TViRYv9ybGuXuki4CusDSzqA=
 go.etcd.io/etcd/etcdctl/v3 v3.5.0-alpha.0/go.mod h1:YPwSaBciV5G6Gpt435AasAG3ROetZsKNUzibRa/++oo=
 go.etcd.io/etcd/pkg/v3 v3.5.0-alpha.0 h1:3yLUEC0nFCxw/RArImOyRUI4OAFbg4PFpBbAhSNzKNY=
@@ -1585,18 +1523,12 @@ go.jolheiser.com/hcaptcha v0.0.4 h1:RrDERcr/Tz/kWyJenjVtI+V09RtLinXxlAemiwN5F+I=
 go.jolheiser.com/hcaptcha v0.0.4/go.mod h1:aw32WQOxnQZ6E06C0LypCf+sxNxPACyOnq+ZGnrIYho=
 go.jolheiser.com/pwn v0.0.3 h1:MQowb3QvCL5r5NmHmCPxw93SdjfgJ0q6rAwYn4i1Hjg=
 go.jolheiser.com/pwn v0.0.3/go.mod h1:/j5Dl8ftNqqJ8Dlx3YTrJV1wIR2lWOTyrNU3Qe7rk6I=
-go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
-go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
-go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
-go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
-go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
-go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
 go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
 go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
-go.mongodb.org/mongo-driver v1.8.2 h1:8ssUXufb90ujcIvR6MyE1SchaNj0SFxsakiZgxIyrMk=
-go.mongodb.org/mongo-driver v1.8.2/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
+go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
+go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
+go.mongodb.org/mongo-driver v1.10.1 h1:NujsPveKwHaWuKUer/ceo9DzEe7HIj1SlJ6uvXZG0S4=
+go.mongodb.org/mongo-driver v1.10.1/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
 go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -1614,8 +1546,8 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
-go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
+go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -1629,11 +1561,11 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
-go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
 go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
+go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
 gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
 golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1641,25 +1573,21 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1669,15 +1597,16 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
-golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
+golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1715,13 +1644,11 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1731,7 +1658,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -1743,7 +1669,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1760,7 +1685,6 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
@@ -1777,20 +1701,25 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
 golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1810,10 +1739,13 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1825,8 +1757,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1841,7 +1774,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1850,7 +1782,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1858,12 +1789,8 @@ golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1875,7 +1802,6 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1903,7 +1829,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1915,11 +1840,9 @@ golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1930,19 +1853,33 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1951,22 +1888,22 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
-golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
+golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -1982,14 +1919,11 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -2046,16 +1980,17 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
 golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -2091,9 +2026,16 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6
 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
-google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2153,6 +2095,7 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210331142528-b7513248f0ba/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
@@ -2173,14 +2116,28 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc
 google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 h1:4SPz2GL2CXJt28MTF8V6Ap/9ZiVbQlJeGSd9qtA7DLs=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -2214,9 +2171,12 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
-google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -2232,8 +2192,9 @@ google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX7
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@@ -2255,9 +2216,8 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a
 gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
-gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
@@ -2279,8 +2239,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -2414,5 +2376,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:
 xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
 xorm.io/builder v0.3.11 h1:naLkJitGyYW7ZZdncsh/JW+HF4HshmvTHTyUyPwJS00=
 xorm.io/builder v0.3.11/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
-xorm.io/xorm v1.3.1 h1:z5egKrDoOLqZFhMjcGF4FBHiTmE5/feQoHclfhNidfM=
-xorm.io/xorm v1.3.1/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
+xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f h1:3NvNsM4lnttTsHpk8ODHqrwN1MCEjsO3bD/rpd8A47k=
+xorm.io/xorm v1.3.2-0.20220714055524-c3bce556200f/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=
diff --git a/integrations/api_packages_generic_test.go b/integrations/api_packages_generic_test.go
deleted file mode 100644
index c507702eaafd1..0000000000000
--- a/integrations/api_packages_generic_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package integrations
-
-import (
-	"bytes"
-	"fmt"
-	"net/http"
-	"testing"
-
-	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/packages"
-	"code.gitea.io/gitea/models/unittest"
-	user_model "code.gitea.io/gitea/models/user"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestPackageGeneric(t *testing.T) {
-	defer prepareTestEnv(t)()
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-
-	packageName := "te-st_pac.kage"
-	packageVersion := "1.0.3"
-	filename := "fi-le_na.me"
-	content := []byte{1, 2, 3}
-
-	url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename)
-
-	t.Run("Upload", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
-		AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusCreated)
-
-		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
-		assert.NoError(t, err)
-		assert.Len(t, pvs, 1)
-
-		pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
-		assert.NoError(t, err)
-		assert.NotNil(t, pd.SemVer)
-		assert.Nil(t, pd.Metadata)
-		assert.Equal(t, packageName, pd.Package.Name)
-		assert.Equal(t, packageVersion, pd.Version.Version)
-
-		pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
-		assert.NoError(t, err)
-		assert.Len(t, pfs, 1)
-		assert.Equal(t, filename, pfs[0].Name)
-		assert.True(t, pfs[0].IsLead)
-
-		pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
-		assert.NoError(t, err)
-		assert.Equal(t, int64(len(content)), pb.Size)
-	})
-
-	t.Run("UploadExists", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
-		AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusBadRequest)
-	})
-
-	t.Run("Download", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "GET", url)
-		resp := MakeRequest(t, req, http.StatusOK)
-
-		assert.Equal(t, content, resp.Body.Bytes())
-
-		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
-		assert.NoError(t, err)
-		assert.Len(t, pvs, 1)
-		assert.Equal(t, int64(1), pvs[0].DownloadCount)
-	})
-
-	t.Run("Delete", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "DELETE", url)
-		AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusOK)
-
-		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGeneric)
-		assert.NoError(t, err)
-		assert.Empty(t, pvs)
-	})
-
-	t.Run("DownloadNotExists", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "GET", url)
-		MakeRequest(t, req, http.StatusNotFound)
-	})
-
-	t.Run("DeleteNotExists", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "DELETE", url)
-		AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusNotFound)
-	})
-}
diff --git a/integrations/api_packages_nuget_test.go b/integrations/api_packages_nuget_test.go
deleted file mode 100644
index e69dd0ff9b669..0000000000000
--- a/integrations/api_packages_nuget_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package integrations
-
-import (
-	"archive/zip"
-	"bytes"
-	"encoding/base64"
-	"fmt"
-	"io"
-	"net/http"
-	"testing"
-
-	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/packages"
-	"code.gitea.io/gitea/models/unittest"
-	user_model "code.gitea.io/gitea/models/user"
-	nuget_module "code.gitea.io/gitea/modules/packages/nuget"
-	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/routers/api/packages/nuget"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestPackageNuGet(t *testing.T) {
-	defer prepareTestEnv(t)()
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-
-	packageName := "test.package"
-	packageVersion := "1.0.3"
-	packageAuthors := "KN4CK3R"
-	packageDescription := "Gitea Test Package"
-	symbolFilename := "test.pdb"
-	symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
-
-	var buf bytes.Buffer
-	archive := zip.NewWriter(&buf)
-	w, _ := archive.Create("package.nuspec")
-	w.Write([]byte(`
-	
-	  
-		` + packageName + ` 
-		` + packageVersion + ` 
-		` + packageAuthors + ` 
-		` + packageDescription + ` 
-		
-			 
-	   
-	 `))
-	archive.Close()
-	content := buf.Bytes()
-
-	url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
-
-	t.Run("ServiceIndex", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
-		req = AddBasicAuthHeader(req, user.Name)
-		resp := MakeRequest(t, req, http.StatusOK)
-
-		var result nuget.ServiceIndexResponse
-		DecodeJSON(t, resp, &result)
-
-		assert.Equal(t, "3.0.0", result.Version)
-		assert.NotEmpty(t, result.Resources)
-
-		root := setting.AppURL + url[1:]
-		for _, r := range result.Resources {
-			switch r.Type {
-			case "SearchQueryService":
-				fallthrough
-			case "SearchQueryService/3.0.0-beta":
-				fallthrough
-			case "SearchQueryService/3.0.0-rc":
-				assert.Equal(t, root+"/query", r.ID)
-			case "RegistrationsBaseUrl":
-				fallthrough
-			case "RegistrationsBaseUrl/3.0.0-beta":
-				fallthrough
-			case "RegistrationsBaseUrl/3.0.0-rc":
-				assert.Equal(t, root+"/registration", r.ID)
-			case "PackageBaseAddress/3.0.0":
-				assert.Equal(t, root+"/package", r.ID)
-			case "PackagePublish/2.0.0":
-				assert.Equal(t, root, r.ID)
-			}
-		}
-	})
-
-	t.Run("Upload", func(t *testing.T) {
-		t.Run("DependencyPackage", func(t *testing.T) {
-			defer PrintCurrentTest(t)()
-
-			req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusCreated)
-
-			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
-			assert.NoError(t, err)
-			assert.Len(t, pvs, 1)
-
-			pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
-			assert.NoError(t, err)
-			assert.NotNil(t, pd.SemVer)
-			assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata)
-			assert.Equal(t, packageName, pd.Package.Name)
-			assert.Equal(t, packageVersion, pd.Version.Version)
-
-			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
-			assert.NoError(t, err)
-			assert.Len(t, pfs, 1)
-			assert.Equal(t, fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion), pfs[0].Name)
-			assert.True(t, pfs[0].IsLead)
-
-			pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
-			assert.NoError(t, err)
-			assert.Equal(t, int64(len(content)), pb.Size)
-
-			req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusBadRequest)
-		})
-
-		t.Run("SymbolPackage", func(t *testing.T) {
-			defer PrintCurrentTest(t)()
-
-			createPackage := func(id, packageType string) io.Reader {
-				var buf bytes.Buffer
-				archive := zip.NewWriter(&buf)
-
-				w, _ := archive.Create("package.nuspec")
-				w.Write([]byte(`
-				
-				
-					` + id + ` 
-					` + packageVersion + ` 
-					` + packageAuthors + ` 
-					` + packageDescription + ` 
-					 
-				 `))
-
-				w, _ = archive.Create(symbolFilename)
-				b, _ := base64.StdEncoding.DecodeString(`QlNKQgEAAQAAAAAADAAAAFBEQiB2MS4wAAAAAAAABgB8AAAAWAAAACNQZGIAAAAA1AAAAAgBAAAj
-fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB
-AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
-				w.Write(b)
-
-				archive.Close()
-				return &buf
-			}
-
-			req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage("unknown-package", "SymbolsPackage"))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusNotFound)
-
-			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "DummyPackage"))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusBadRequest)
-
-			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusCreated)
-
-			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
-			assert.NoError(t, err)
-			assert.Len(t, pvs, 1)
-
-			pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
-			assert.NoError(t, err)
-			assert.NotNil(t, pd.SemVer)
-			assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata)
-			assert.Equal(t, packageName, pd.Package.Name)
-			assert.Equal(t, packageVersion, pd.Version.Version)
-
-			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
-			assert.NoError(t, err)
-			assert.Len(t, pfs, 3)
-			for _, pf := range pfs {
-				switch pf.Name {
-				case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion):
-				case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion):
-					assert.False(t, pf.IsLead)
-
-					pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
-					assert.NoError(t, err)
-					assert.Equal(t, int64(616), pb.Size)
-				case symbolFilename:
-					assert.False(t, pf.IsLead)
-
-					pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
-					assert.NoError(t, err)
-					assert.Equal(t, int64(160), pb.Size)
-
-					pps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID)
-					assert.NoError(t, err)
-					assert.Len(t, pps, 1)
-					assert.Equal(t, nuget_module.PropertySymbolID, pps[0].Name)
-					assert.Equal(t, symbolID, pps[0].Value)
-				default:
-					assert.Fail(t, "unexpected file: %v", pf.Name)
-				}
-			}
-
-			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createPackage(packageName, "SymbolsPackage"))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusBadRequest)
-		})
-	})
-
-	t.Run("Download", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		checkDownloadCount := func(count int64) {
-			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
-			assert.NoError(t, err)
-			assert.Len(t, pvs, 1)
-			assert.Equal(t, count, pvs[0].DownloadCount)
-		}
-
-		checkDownloadCount(0)
-
-		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		resp := MakeRequest(t, req, http.StatusOK)
-
-		assert.Equal(t, content, resp.Body.Bytes())
-
-		checkDownloadCount(1)
-
-		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusOK)
-
-		checkDownloadCount(1)
-
-		t.Run("Symbol", func(t *testing.T) {
-			defer PrintCurrentTest(t)()
-
-			req := NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/gitea.pdb", url, symbolFilename, symbolID))
-			MakeRequest(t, req, http.StatusBadRequest)
-
-			req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, "00000000000000000000000000000000", symbolFilename))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusNotFound)
-
-			req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, symbolID, symbolFilename))
-			req = AddBasicAuthHeader(req, user.Name)
-			MakeRequest(t, req, http.StatusOK)
-
-			checkDownloadCount(1)
-		})
-	})
-
-	t.Run("SearchService", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		cases := []struct {
-			Query           string
-			Skip            int
-			Take            int
-			ExpectedTotal   int64
-			ExpectedResults int
-		}{
-			{"", 0, 0, 1, 1},
-			{"", 0, 10, 1, 1},
-			{"gitea", 0, 10, 0, 0},
-			{"test", 0, 10, 1, 1},
-			{"test", 1, 10, 1, 0},
-		}
-
-		for i, c := range cases {
-			req := NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
-			req = AddBasicAuthHeader(req, user.Name)
-			resp := MakeRequest(t, req, http.StatusOK)
-
-			var result nuget.SearchResultResponse
-			DecodeJSON(t, resp, &result)
-
-			assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
-			assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
-		}
-	})
-
-	t.Run("RegistrationService", func(t *testing.T) {
-		indexURL := fmt.Sprintf("%s%s/registration/%s/index.json", setting.AppURL, url[1:], packageName)
-		leafURL := fmt.Sprintf("%s%s/registration/%s/%s.json", setting.AppURL, url[1:], packageName, packageVersion)
-		contentURL := fmt.Sprintf("%s%s/package/%s/%s/%s.%s.nupkg", setting.AppURL, url[1:], packageName, packageVersion, packageName, packageVersion)
-
-		t.Run("RegistrationIndex", func(t *testing.T) {
-			defer PrintCurrentTest(t)()
-
-			req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/index.json", url, packageName))
-			req = AddBasicAuthHeader(req, user.Name)
-			resp := MakeRequest(t, req, http.StatusOK)
-
-			var result nuget.RegistrationIndexResponse
-			DecodeJSON(t, resp, &result)
-
-			assert.Equal(t, indexURL, result.RegistrationIndexURL)
-			assert.Equal(t, 1, result.Count)
-			assert.Len(t, result.Pages, 1)
-			assert.Equal(t, indexURL, result.Pages[0].RegistrationPageURL)
-			assert.Equal(t, packageVersion, result.Pages[0].Lower)
-			assert.Equal(t, packageVersion, result.Pages[0].Upper)
-			assert.Equal(t, 1, result.Pages[0].Count)
-			assert.Len(t, result.Pages[0].Items, 1)
-			assert.Equal(t, packageName, result.Pages[0].Items[0].CatalogEntry.ID)
-			assert.Equal(t, packageVersion, result.Pages[0].Items[0].CatalogEntry.Version)
-			assert.Equal(t, packageAuthors, result.Pages[0].Items[0].CatalogEntry.Authors)
-			assert.Equal(t, packageDescription, result.Pages[0].Items[0].CatalogEntry.Description)
-			assert.Equal(t, leafURL, result.Pages[0].Items[0].CatalogEntry.CatalogLeafURL)
-			assert.Equal(t, contentURL, result.Pages[0].Items[0].CatalogEntry.PackageContentURL)
-		})
-
-		t.Run("RegistrationLeaf", func(t *testing.T) {
-			defer PrintCurrentTest(t)()
-
-			req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/%s.json", url, packageName, packageVersion))
-			req = AddBasicAuthHeader(req, user.Name)
-			resp := MakeRequest(t, req, http.StatusOK)
-
-			var result nuget.RegistrationLeafResponse
-			DecodeJSON(t, resp, &result)
-
-			assert.Equal(t, leafURL, result.RegistrationLeafURL)
-			assert.Equal(t, contentURL, result.PackageContentURL)
-			assert.Equal(t, indexURL, result.RegistrationIndexURL)
-		})
-	})
-
-	t.Run("PackageService", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/index.json", url, packageName))
-		req = AddBasicAuthHeader(req, user.Name)
-		resp := MakeRequest(t, req, http.StatusOK)
-
-		var result nuget.PackageVersionsResponse
-		DecodeJSON(t, resp, &result)
-
-		assert.Len(t, result.Versions, 1)
-		assert.Equal(t, packageVersion, result.Versions[0])
-	})
-
-	t.Run("Delete", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusOK)
-
-		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
-		assert.NoError(t, err)
-		assert.Empty(t, pvs)
-	})
-
-	t.Run("DownloadNotExists", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusNotFound)
-
-		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusNotFound)
-	})
-
-	t.Run("DeleteNotExists", func(t *testing.T) {
-		defer PrintCurrentTest(t)()
-
-		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s", url, packageName, packageVersion))
-		req = AddBasicAuthHeader(req, user.Name)
-		MakeRequest(t, req, http.StatusNotFound)
-	})
-}
diff --git a/integrations/migrate_test.go b/integrations/migrate_test.go
deleted file mode 100644
index 9b59c85a4eb7e..0000000000000
--- a/integrations/migrate_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package integrations
-
-import (
-	"os"
-	"testing"
-
-	"code.gitea.io/gitea/models/unittest"
-	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/services/migrations"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestMigrateLocalPath(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}).(*user_model.User)
-
-	old := setting.ImportLocalPaths
-	setting.ImportLocalPaths = true
-
-	lowercasePath, err := os.MkdirTemp("", "lowercase") // may not be lowercase because MkdirTemp creates a random directory name which may be mixedcase
-	assert.NoError(t, err)
-	defer os.RemoveAll(lowercasePath)
-
-	err = migrations.IsMigrateURLAllowed(lowercasePath, adminUser)
-	assert.NoError(t, err, "case lowercase path")
-
-	mixedcasePath, err := os.MkdirTemp("", "mIxeDCaSe")
-	assert.NoError(t, err)
-	defer os.RemoveAll(mixedcasePath)
-
-	err = migrations.IsMigrateURLAllowed(mixedcasePath, adminUser)
-	assert.NoError(t, err, "case mixedcase path")
-
-	setting.ImportLocalPaths = old
-}
diff --git a/integrations/oauth_test.go b/integrations/oauth_test.go
deleted file mode 100644
index 678dfbae2d486..0000000000000
--- a/integrations/oauth_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package integrations
-
-import (
-	"bytes"
-	"io"
-	"net/http"
-	"testing"
-
-	"code.gitea.io/gitea/modules/json"
-	"code.gitea.io/gitea/modules/setting"
-
-	"github.com/stretchr/testify/assert"
-)
-
-const defaultAuthorize = "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate"
-
-func TestNoClientID(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequest(t, "GET", "/login/oauth/authorize")
-	ctx := loginUser(t, "user2")
-	ctx.MakeRequest(t, req, http.StatusBadRequest)
-}
-
-func TestLoginRedirect(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequest(t, "GET", "/login/oauth/authorize")
-	assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
-}
-
-func TestShowAuthorize(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequest(t, "GET", defaultAuthorize)
-	ctx := loginUser(t, "user4")
-	resp := ctx.MakeRequest(t, req, http.StatusOK)
-
-	htmlDoc := NewHTMLParser(t, resp.Body)
-	htmlDoc.AssertElement(t, "#authorize-app", true)
-	htmlDoc.GetCSRF()
-}
-
-func TestRedirectWithExistingGrant(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequest(t, "GET", defaultAuthorize)
-	ctx := loginUser(t, "user1")
-	resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
-	u, err := resp.Result().Location()
-	assert.NoError(t, err)
-	assert.Equal(t, "thestate", u.Query().Get("state"))
-	assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
-}
-
-func TestAccessTokenExchange(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	resp := MakeRequest(t, req, http.StatusOK)
-	type response struct {
-		AccessToken  string `json:"access_token"`
-		TokenType    string `json:"token_type"`
-		ExpiresIn    int64  `json:"expires_in"`
-		RefreshToken string `json:"refresh_token"`
-	}
-	parsed := new(response)
-
-	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
-	assert.True(t, len(parsed.AccessToken) > 10)
-	assert.True(t, len(parsed.RefreshToken) > 10)
-}
-
-func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	resp := MakeRequest(t, req, http.StatusOK)
-	type response struct {
-		AccessToken  string `json:"access_token"`
-		TokenType    string `json:"token_type"`
-		ExpiresIn    int64  `json:"expires_in"`
-		RefreshToken string `json:"refresh_token"`
-	}
-	parsed := new(response)
-
-	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
-	assert.True(t, len(parsed.AccessToken) > 10)
-	assert.True(t, len(parsed.RefreshToken) > 10)
-}
-
-func TestAccessTokenExchangeJSON(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-}
-
-func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
-	defer prepareTestEnv(t)()
-	// invalid client id
-	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "???",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-	// invalid client secret
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "???",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-	// invalid redirect uri
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "???",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-	// invalid authorization code
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "???",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-	// invalid grant_type
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "???",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	MakeRequest(t, req, http.StatusBadRequest)
-}
-
-func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
-	resp := MakeRequest(t, req, http.StatusOK)
-	type response struct {
-		AccessToken  string `json:"access_token"`
-		TokenType    string `json:"token_type"`
-		ExpiresIn    int64  `json:"expires_in"`
-		RefreshToken string `json:"refresh_token"`
-	}
-	parsed := new(response)
-
-	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
-	assert.True(t, len(parsed.AccessToken) > 10)
-	assert.True(t, len(parsed.RefreshToken) > 10)
-
-	// use wrong client_secret
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==")
-	resp = MakeRequest(t, req, http.StatusBadRequest)
-
-	// missing header
-	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	resp = MakeRequest(t, req, http.StatusBadRequest)
-}
-
-func TestRefreshTokenInvalidation(t *testing.T) {
-	defer prepareTestEnv(t)()
-	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "authorization_code",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"code":          "authcode",
-		"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
-	})
-	resp := MakeRequest(t, req, http.StatusOK)
-	type response struct {
-		AccessToken  string `json:"access_token"`
-		TokenType    string `json:"token_type"`
-		ExpiresIn    int64  `json:"expires_in"`
-		RefreshToken string `json:"refresh_token"`
-	}
-	parsed := new(response)
-
-	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
-
-	// test without invalidation
-	setting.OAuth2.InvalidateRefreshTokens = false
-
-	refreshReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
-		"grant_type":    "refresh_token",
-		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
-		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
-		"redirect_uri":  "a",
-		"refresh_token": parsed.RefreshToken,
-	})
-
-	bs, err := io.ReadAll(refreshReq.Body)
-	assert.NoError(t, err)
-
-	refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
-	MakeRequest(t, refreshReq, http.StatusOK)
-
-	refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
-	MakeRequest(t, refreshReq, http.StatusOK)
-
-	// test with invalidation
-	setting.OAuth2.InvalidateRefreshTokens = true
-	refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
-	MakeRequest(t, refreshReq, http.StatusOK)
-
-	refreshReq.Body = io.NopCloser(bytes.NewReader(bs))
-	MakeRequest(t, refreshReq, http.StatusBadRequest)
-}
diff --git a/jest.config.js b/jest.config.js
deleted file mode 100644
index d24333aa35249..0000000000000
--- a/jest.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export default {
-  rootDir: 'web_src',
-  setupFilesAfterEnv: ['jest-extended/all'],
-  testEnvironment: '@happy-dom/jest-environment',
-  testMatch: ['
/**/*.test.js'],
-  testTimeout: 20000,
-  transform: {
-    '\\.svg$': '/js/testUtils/jestRawLoader.js',
-  },
-  verbose: false,
-};
-
diff --git a/main.go b/main.go
index ac60f85a6680b..0e550f05ebca2 100644
--- a/main.go
+++ b/main.go
@@ -171,9 +171,9 @@ func setAppHelpTemplates() {
 }
 
 func adjustHelpTemplate(originalTemplate string) string {
-	overrided := ""
+	overridden := ""
 	if _, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
-		overrided = "(GITEA_CUSTOM)"
+		overridden = "(GITEA_CUSTOM)"
 	}
 
 	return fmt.Sprintf(`%s
@@ -183,7 +183,7 @@ DEFAULT CONFIGURATION:
      AppPath:     %s
      AppWorkPath: %s
 
-`, originalTemplate, setting.CustomPath, overrided, setting.CustomConf, setting.AppPath, setting.AppWorkPath)
+`, originalTemplate, setting.CustomPath, overridden, setting.CustomConf, setting.AppPath, setting.AppWorkPath)
 }
 
 func formatBuiltWith() string {
diff --git a/models/action.go b/models/activities/action.go
similarity index 90%
rename from models/action.go
rename to models/activities/action.go
index 78cc93e1a2144..5ff0079a6b1e6 100644
--- a/models/action.go
+++ b/models/activities/action.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities
 
 import (
 	"context"
@@ -92,13 +92,20 @@ func init() {
 
 // TableIndices implements xorm's TableIndices interface
 func (a *Action) TableIndices() []*schemas.Index {
+	repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
+	repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
+
 	actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
 	actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
 
-	repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
-	repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
+	indices := []*schemas.Index{actUserIndex, repoIndex}
+	if setting.Database.UsePostgreSQL {
+		cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
+		cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
+		indices = append(indices, cudIndex)
+	}
 
-	return []*schemas.Index{actUserIndex, repoIndex}
+	return indices
 }
 
 // GetOpType gets the ActionType of this action.
@@ -211,19 +218,9 @@ func (a *Action) GetRepoLink() string {
 	return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName()))
 }
 
-// GetRepositoryFromMatch returns a *repo_model.Repository from a username and repo strings
-func GetRepositoryFromMatch(ownerName, repoName string) (*repo_model.Repository, error) {
-	var err error
-	refRepo, err := repo_model.GetRepositoryByOwnerAndName(ownerName, repoName)
-	if err != nil {
-		if repo_model.IsErrRepoNotExist(err) {
-			log.Warn("Repository referenced in commit but does not exist: %v", err)
-			return nil, err
-		}
-		log.Error("repo_model.GetRepositoryByOwnerAndName: %v", err)
-		return nil, err
-	}
-	return refRepo, nil
+// GetRepoAbsoluteLink returns the absolute link to action repository.
+func (a *Action) GetRepoAbsoluteLink() string {
+	return setting.AppURL + url.PathEscape(a.GetRepoUserName()) + "/" + url.PathEscape(a.GetRepoName())
 }
 
 // GetCommentLink returns link to action comment.
@@ -275,7 +272,7 @@ func (a *Action) GetRefLink() string {
 		return a.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.BranchPrefix))
 	case strings.HasPrefix(a.RefName, git.TagPrefix):
 		return a.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(strings.TrimPrefix(a.RefName, git.TagPrefix))
-	case len(a.RefName) == 40 && git.SHAPattern.MatchString(a.RefName):
+	case len(a.RefName) == git.SHAFullLength && git.IsValidSHAPattern(a.RefName):
 		return a.GetRepoLink() + "/src/commit/" + a.RefName
 	default:
 		// FIXME: we will just assume it's a branch - this was the old way - at some point we may want to enforce that there is always a ref here.
@@ -362,17 +359,18 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, error) {
 	actions := make([]*Action, 0, opts.PageSize)
 
 	if err := sess.Desc("`action`.created_unix").Find(&actions); err != nil {
-		return nil, fmt.Errorf("Find: %v", err)
+		return nil, fmt.Errorf("Find: %w", err)
 	}
 
 	if err := ActionList(actions).loadAttributes(ctx); err != nil {
-		return nil, fmt.Errorf("LoadAttributes: %v", err)
+		return nil, fmt.Errorf("LoadAttributes: %w", err)
 	}
 
 	return actions, nil
 }
 
-func activityReadable(user, doer *user_model.User) bool {
+// ActivityReadable return whether doer can read activities of user
+func ActivityReadable(user, doer *user_model.User) bool {
 	return !user.KeepActivityPrivate ||
 		doer != nil && (doer.IsAdmin || user.ID == doer.ID)
 }
@@ -417,7 +415,7 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
 		env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(opts.RequestedTeam)
 		teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
 		if err != nil {
-			return nil, fmt.Errorf("GetTeamRepositories: %v", err)
+			return nil, fmt.Errorf("GetTeamRepositories: %w", err)
 		}
 		cond = cond.And(builder.In("repo_id", teamRepoIDs))
 	}
@@ -459,7 +457,7 @@ func DeleteOldActions(olderThan time.Duration) (err error) {
 	}
 
 	_, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{})
-	return
+	return err
 }
 
 func notifyWatchers(ctx context.Context, actions ...*Action) error {
@@ -479,14 +477,14 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
 			// Add feeds for user self and all watchers.
 			watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
 			if err != nil {
-				return fmt.Errorf("get watchers: %v", err)
+				return fmt.Errorf("get watchers: %w", err)
 			}
 		}
 
 		// Add feed for actioner.
 		act.UserID = act.ActUserID
 		if _, err = e.Insert(act); err != nil {
-			return fmt.Errorf("insert new actioner: %v", err)
+			return fmt.Errorf("insert new actioner: %w", err)
 		}
 
 		if repoChanged {
@@ -495,7 +493,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
 
 			// check repo owner exist.
 			if err := act.Repo.GetOwner(ctx); err != nil {
-				return fmt.Errorf("can't get repo owner: %v", err)
+				return fmt.Errorf("can't get repo owner: %w", err)
 			}
 		} else if act.Repo == nil {
 			act.Repo = repo
@@ -506,7 +504,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
 			act.ID = 0
 			act.UserID = act.Repo.Owner.ID
 			if err = db.Insert(ctx, act); err != nil {
-				return fmt.Errorf("insert new actioner: %v", err)
+				return fmt.Errorf("insert new actioner: %w", err)
 			}
 		}
 
@@ -559,7 +557,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
 			}
 
 			if err = db.Insert(ctx, act); err != nil {
-				return fmt.Errorf("insert new action: %v", err)
+				return fmt.Errorf("insert new action: %w", err)
 			}
 		}
 	}
@@ -602,3 +600,23 @@ func DeleteIssueActions(ctx context.Context, repoID, issueID int64) error {
 		Delete(&Action{})
 	return err
 }
+
+// CountActionCreatedUnixString count actions where created_unix is an empty string
+func CountActionCreatedUnixString() (int64, error) {
+	if setting.Database.UseSQLite3 {
+		return db.GetEngine(db.DefaultContext).Where(`created_unix = ""`).Count(new(Action))
+	}
+	return 0, nil
+}
+
+// FixActionCreatedUnixString set created_unix to zero if it is an empty string
+func FixActionCreatedUnixString() (int64, error) {
+	if setting.Database.UseSQLite3 {
+		res, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
+		if err != nil {
+			return 0, err
+		}
+		return res.RowsAffected()
+	}
+	return 0, nil
+}
diff --git a/models/action_list.go b/models/activities/action_list.go
similarity index 83%
rename from models/action_list.go
rename to models/activities/action_list.go
index d585ef0fc26cc..86aa8689e2f00 100644
--- a/models/action_list.go
+++ b/models/activities/action_list.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities
 
 import (
 	"context"
@@ -18,13 +18,11 @@ import (
 type ActionList []*Action
 
 func (actions ActionList) getUserIDs() []int64 {
-	userIDs := make(map[int64]struct{}, len(actions))
+	userIDs := make(container.Set[int64], len(actions))
 	for _, action := range actions {
-		if _, ok := userIDs[action.ActUserID]; !ok {
-			userIDs[action.ActUserID] = struct{}{}
-		}
+		userIDs.Add(action.ActUserID)
 	}
-	return container.KeysInt64(userIDs)
+	return userIDs.Values()
 }
 
 func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
@@ -38,7 +36,7 @@ func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.
 		In("id", userIDs).
 		Find(&userMaps)
 	if err != nil {
-		return nil, fmt.Errorf("find user: %v", err)
+		return nil, fmt.Errorf("find user: %w", err)
 	}
 
 	for _, action := range actions {
@@ -48,13 +46,11 @@ func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.
 }
 
 func (actions ActionList) getRepoIDs() []int64 {
-	repoIDs := make(map[int64]struct{}, len(actions))
+	repoIDs := make(container.Set[int64], len(actions))
 	for _, action := range actions {
-		if _, ok := repoIDs[action.RepoID]; !ok {
-			repoIDs[action.RepoID] = struct{}{}
-		}
+		repoIDs.Add(action.RepoID)
 	}
-	return container.KeysInt64(repoIDs)
+	return repoIDs.Values()
 }
 
 func (actions ActionList) loadRepositories(ctx context.Context) error {
@@ -66,7 +62,7 @@ func (actions ActionList) loadRepositories(ctx context.Context) error {
 	repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
 	err := db.GetEngine(ctx).In("id", repoIDs).Find(&repoMaps)
 	if err != nil {
-		return fmt.Errorf("find repository: %v", err)
+		return fmt.Errorf("find repository: %w", err)
 	}
 
 	for _, action := range actions {
diff --git a/models/action_test.go b/models/activities/action_test.go
similarity index 72%
rename from models/action_test.go
rename to models/activities/action_test.go
index 2d46bd3e80e11..ac2a3043a6a27 100644
--- a/models/action_test.go
+++ b/models/activities/action_test.go
@@ -2,13 +2,15 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities_test
 
 import (
 	"path"
 	"testing"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
+	issue_model "code.gitea.io/gitea/models/issues"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -19,28 +21,31 @@ import (
 
 func TestAction_GetRepoPath(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}).(*repo_model.Repository)
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
-	action := &Action{RepoID: repo.ID}
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+	action := &activities_model.Action{RepoID: repo.ID}
 	assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath())
 }
 
 func TestAction_GetRepoLink(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{}).(*repo_model.Repository)
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
-	action := &Action{RepoID: repo.ID}
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+	comment := unittest.AssertExistsAndLoadBean(t, &issue_model.Comment{ID: 2})
+	action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID}
 	setting.AppSubURL = "/suburl"
 	expected := path.Join(setting.AppSubURL, owner.Name, repo.Name)
 	assert.Equal(t, expected, action.GetRepoLink())
+	assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink())
+	assert.Equal(t, comment.HTMLURL(), action.GetCommentLink())
 }
 
 func TestGetFeeds(t *testing.T) {
 	// test with an individual user
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
-	actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedUser:   user,
 		Actor:           user,
 		IncludePrivate:  true,
@@ -53,7 +58,7 @@ func TestGetFeeds(t *testing.T) {
 		assert.EqualValues(t, user.ID, actions[0].UserID)
 	}
 
-	actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedUser:   user,
 		Actor:           user,
 		IncludePrivate:  false,
@@ -65,12 +70,12 @@ func TestGetFeeds(t *testing.T) {
 
 func TestGetFeedsForRepos(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
-	pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}).(*repo_model.Repository)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+	pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8})
 
 	// private repo & no login
-	actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedRepo:  privRepo,
 		IncludePrivate: true,
 	})
@@ -78,7 +83,7 @@ func TestGetFeedsForRepos(t *testing.T) {
 	assert.Len(t, actions, 0)
 
 	// public repo & no login
-	actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedRepo:  pubRepo,
 		IncludePrivate: true,
 	})
@@ -86,7 +91,7 @@ func TestGetFeedsForRepos(t *testing.T) {
 	assert.Len(t, actions, 1)
 
 	// private repo and login
-	actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedRepo:  privRepo,
 		IncludePrivate: true,
 		Actor:          user,
@@ -95,7 +100,7 @@ func TestGetFeedsForRepos(t *testing.T) {
 	assert.Len(t, actions, 1)
 
 	// public repo & login
-	actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedRepo:  pubRepo,
 		IncludePrivate: true,
 		Actor:          user,
@@ -107,10 +112,10 @@ func TestGetFeedsForRepos(t *testing.T) {
 func TestGetFeeds2(t *testing.T) {
 	// test with an organization user
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
-	actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedUser:   org,
 		Actor:           user,
 		IncludePrivate:  true,
@@ -124,7 +129,7 @@ func TestGetFeeds2(t *testing.T) {
 		assert.EqualValues(t, org.ID, actions[0].UserID)
 	}
 
-	actions, err = GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err = activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedUser:   org,
 		Actor:           user,
 		IncludePrivate:  false,
@@ -171,40 +176,40 @@ func TestActivityReadable(t *testing.T) {
 		result: true,
 	}}
 	for _, test := range tt {
-		assert.Equal(t, test.result, activityReadable(test.user, test.doer), test.desc)
+		assert.Equal(t, test.result, activities_model.ActivityReadable(test.user, test.doer), test.desc)
 	}
 }
 
 func TestNotifyWatchers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	action := &Action{
+	action := &activities_model.Action{
 		ActUserID: 8,
 		RepoID:    1,
-		OpType:    ActionStarRepo,
+		OpType:    activities_model.ActionStarRepo,
 	}
-	assert.NoError(t, NotifyWatchers(action))
+	assert.NoError(t, activities_model.NotifyWatchers(action))
 
 	// One watchers are inactive, thus action is only created for user 8, 1, 4, 11
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ActUserID: action.ActUserID,
 		UserID:    8,
 		RepoID:    action.RepoID,
 		OpType:    action.OpType,
 	})
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ActUserID: action.ActUserID,
 		UserID:    1,
 		RepoID:    action.RepoID,
 		OpType:    action.OpType,
 	})
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ActUserID: action.ActUserID,
 		UserID:    4,
 		RepoID:    action.RepoID,
 		OpType:    action.OpType,
 	})
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ActUserID: action.ActUserID,
 		UserID:    11,
 		RepoID:    action.RepoID,
@@ -214,13 +219,13 @@ func TestNotifyWatchers(t *testing.T) {
 
 func TestGetFeedsCorrupted(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ID:     8,
 		RepoID: 1700,
 	})
 
-	actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
+	actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 		RequestedUser:  user,
 		Actor:          user,
 		IncludePrivate: true,
@@ -235,12 +240,12 @@ func TestConsistencyUpdateAction(t *testing.T) {
 	}
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	id := 8
-	unittest.AssertExistsAndLoadBean(t, &Action{
+	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 		ID: int64(id),
 	})
 	_, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = "" WHERE id = ?`, id)
 	assert.NoError(t, err)
-	actions := make([]*Action, 0, 1)
+	actions := make([]*activities_model.Action, 0, 1)
 	//
 	// XORM returns an error when created_unix is a string
 	//
@@ -251,17 +256,17 @@ func TestConsistencyUpdateAction(t *testing.T) {
 	//
 	// Get rid of incorrectly set created_unix
 	//
-	count, err := CountActionCreatedUnixString()
+	count, err := activities_model.CountActionCreatedUnixString()
 	assert.NoError(t, err)
 	assert.EqualValues(t, 1, count)
-	count, err = FixActionCreatedUnixString()
+	count, err = activities_model.FixActionCreatedUnixString()
 	assert.NoError(t, err)
 	assert.EqualValues(t, 1, count)
 
-	count, err = CountActionCreatedUnixString()
+	count, err = activities_model.CountActionCreatedUnixString()
 	assert.NoError(t, err)
 	assert.EqualValues(t, 0, count)
-	count, err = FixActionCreatedUnixString()
+	count, err = activities_model.FixActionCreatedUnixString()
 	assert.NoError(t, err)
 	assert.EqualValues(t, 0, count)
 
@@ -269,5 +274,5 @@ func TestConsistencyUpdateAction(t *testing.T) {
 	// XORM must be happy now
 	//
 	assert.NoError(t, db.GetEngine(db.DefaultContext).Where("id = ?", id).Find(&actions))
-	unittest.CheckConsistencyFor(t, &Action{})
+	unittest.CheckConsistencyFor(t, &activities_model.Action{})
 }
diff --git a/models/admin/main_test.go b/models/activities/main_test.go
similarity index 86%
rename from models/admin/main_test.go
rename to models/activities/main_test.go
index 693b70fbf7581..0a87f47600b08 100644
--- a/models/admin/main_test.go
+++ b/models/activities/main_test.go
@@ -2,18 +2,19 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package admin
+package activities_test
 
 import (
 	"path/filepath"
 	"testing"
 
 	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models"
 )
 
 func TestMain(m *testing.M) {
 	unittest.MainTest(m, &unittest.TestOptions{
 		GiteaRootPath: filepath.Join("..", ".."),
-		FixtureFiles:  []string{"notice.yml"},
 	})
 }
diff --git a/models/notification.go b/models/activities/notification.go
similarity index 94%
rename from models/notification.go
rename to models/activities/notification.go
index 3f0e374b8373c..5748b807a0687 100644
--- a/models/notification.go
+++ b/models/activities/notification.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities
 
 import (
 	"context"
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -131,7 +132,7 @@ func (opts *FindNotificationOptions) ToSession(ctx context.Context) *xorm.Sessio
 // GetNotifications returns all notifications that fit to the given options.
 func GetNotifications(ctx context.Context, options *FindNotificationOptions) (nl NotificationList, err error) {
 	err = options.ToSession(ctx).OrderBy("notification.updated_unix DESC").Find(&nl)
-	return
+	return nl, err
 }
 
 // CountNotifications count all notifications that fit to the given options and ignore pagination.
@@ -199,7 +200,7 @@ func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID,
 
 func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
 	// init
-	var toNotify map[int64]struct{}
+	var toNotify container.Set[int64]
 	notifications, err := getNotificationsByIssueID(ctx, issueID)
 	if err != nil {
 		return err
@@ -211,33 +212,27 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
 	}
 
 	if receiverID > 0 {
-		toNotify = make(map[int64]struct{}, 1)
-		toNotify[receiverID] = struct{}{}
+		toNotify = make(container.Set[int64], 1)
+		toNotify.Add(receiverID)
 	} else {
-		toNotify = make(map[int64]struct{}, 32)
+		toNotify = make(container.Set[int64], 32)
 		issueWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, true)
 		if err != nil {
 			return err
 		}
-		for _, id := range issueWatches {
-			toNotify[id] = struct{}{}
-		}
+		toNotify.AddMultiple(issueWatches...)
 		if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) {
 			repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID)
 			if err != nil {
 				return err
 			}
-			for _, id := range repoWatches {
-				toNotify[id] = struct{}{}
-			}
+			toNotify.AddMultiple(repoWatches...)
 		}
 		issueParticipants, err := issue.GetParticipantIDsByIssue(ctx)
 		if err != nil {
 			return err
 		}
-		for _, id := range issueParticipants {
-			toNotify[id] = struct{}{}
-		}
+		toNotify.AddMultiple(issueParticipants...)
 
 		// dont notify user who cause notification
 		delete(toNotify, notificationAuthorID)
@@ -247,7 +242,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
 			return err
 		}
 		for _, id := range issueUnWatches {
-			delete(toNotify, id)
+			toNotify.Remove(id)
 		}
 	}
 
@@ -267,10 +262,10 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
 
 			return err
 		}
-		if issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) {
+		if issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypePullRequests) {
 			continue
 		}
-		if !issue.IsPull && !CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) {
+		if !issue.IsPull && !access_model.CheckRepoUnitUser(ctx, issue.Repo, user, unit.TypeIssues) {
 			continue
 		}
 
@@ -291,7 +286,7 @@ func getNotificationsByIssueID(ctx context.Context, issueID int64) (notification
 	err = db.GetEngine(ctx).
 		Where("issue_id = ?", issueID).
 		Find(¬ifications)
-	return
+	return notifications, err
 }
 
 func notificationExists(notifications []*Notification, issueID, userID int64) bool {
@@ -370,7 +365,7 @@ func NotificationsForUser(ctx context.Context, user *user_model.User, statuses [
 	}
 
 	err = sess.Find(¬ifications)
-	return
+	return notifications, err
 }
 
 // CountUnread count unread notifications for a user
@@ -401,14 +396,14 @@ func (n *Notification) loadAttributes(ctx context.Context) (err error) {
 	if err = n.loadComment(ctx); err != nil {
 		return
 	}
-	return
+	return err
 }
 
 func (n *Notification) loadRepo(ctx context.Context) (err error) {
 	if n.Repository == nil {
 		n.Repository, err = repo_model.GetRepositoryByIDCtx(ctx, n.RepoID)
 		if err != nil {
-			return fmt.Errorf("getRepositoryByID [%d]: %v", n.RepoID, err)
+			return fmt.Errorf("getRepositoryByID [%d]: %w", n.RepoID, err)
 		}
 	}
 	return nil
@@ -418,7 +413,7 @@ func (n *Notification) loadIssue(ctx context.Context) (err error) {
 	if n.Issue == nil && n.IssueID != 0 {
 		n.Issue, err = issues_model.GetIssueByID(ctx, n.IssueID)
 		if err != nil {
-			return fmt.Errorf("getIssueByID [%d]: %v", n.IssueID, err)
+			return fmt.Errorf("getIssueByID [%d]: %w", n.IssueID, err)
 		}
 		return n.Issue.LoadAttributes(ctx)
 	}
@@ -445,7 +440,7 @@ func (n *Notification) loadUser(ctx context.Context) (err error) {
 	if n.User == nil {
 		n.User, err = user_model.GetUserByIDCtx(ctx, n.UserID)
 		if err != nil {
-			return fmt.Errorf("getUserByID [%d]: %v", n.UserID, err)
+			return fmt.Errorf("getUserByID [%d]: %w", n.UserID, err)
 		}
 	}
 	return nil
@@ -498,16 +493,14 @@ func (nl NotificationList) LoadAttributes() error {
 }
 
 func (nl NotificationList) getPendingRepoIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.Repository != nil {
 			continue
 		}
-		if _, ok := ids[notification.RepoID]; !ok {
-			ids[notification.RepoID] = struct{}{}
-		}
+		ids.Add(notification.RepoID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadRepos loads repositories from database
@@ -574,16 +567,14 @@ func (nl NotificationList) LoadRepos() (repo_model.RepositoryList, []int, error)
 }
 
 func (nl NotificationList) getPendingIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.Issue != nil {
 			continue
 		}
-		if _, ok := ids[notification.IssueID]; !ok {
-			ids[notification.IssueID] = struct{}{}
-		}
+		ids.Add(notification.IssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadIssues loads issues from database
@@ -660,16 +651,14 @@ func (nl NotificationList) Without(failures []int) NotificationList {
 }
 
 func (nl NotificationList) getPendingCommentIDs() []int64 {
-	ids := make(map[int64]struct{}, len(nl))
+	ids := make(container.Set[int64], len(nl))
 	for _, notification := range nl {
 		if notification.CommentID == 0 || notification.Comment != nil {
 			continue
 		}
-		if _, ok := ids[notification.CommentID]; !ok {
-			ids[notification.CommentID] = struct{}{}
-		}
+		ids.Add(notification.CommentID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // LoadComments loads comments from database
@@ -730,7 +719,7 @@ func GetNotificationCount(ctx context.Context, user *user_model.User, status Not
 		Where("user_id = ?", user.ID).
 		And("status = ?", status).
 		Count(&Notification{})
-	return
+	return count, err
 }
 
 // UserIDCount is a simple coalition of UserID and Count
@@ -817,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
diff --git a/models/notification_test.go b/models/activities/notification_test.go
similarity index 52%
rename from models/notification_test.go
rename to models/activities/notification_test.go
index 16ff02d6c05b9..4ee16af076e5a 100644
--- a/models/notification_test.go
+++ b/models/activities/notification_test.go
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities_test
 
 import (
 	"testing"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/unittest"
@@ -17,24 +18,24 @@ import (
 
 func TestCreateOrUpdateIssueNotifications(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 
-	assert.NoError(t, CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0))
+	assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0))
 
 	// User 9 is inactive, thus notifications for user 1 and 4 are created
-	notf := unittest.AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
-	assert.Equal(t, NotificationStatusUnread, notf.Status)
+	notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{UserID: 1, IssueID: issue.ID})
+	assert.Equal(t, activities_model.NotificationStatusUnread, notf.Status)
 	unittest.CheckConsistencyFor(t, &issues_model.Issue{ID: issue.ID})
 
-	notf = unittest.AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
-	assert.Equal(t, NotificationStatusUnread, notf.Status)
+	notf = unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{UserID: 4, IssueID: issue.ID})
+	assert.Equal(t, activities_model.NotificationStatusUnread, notf.Status)
 }
 
 func TestNotificationsForUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
-	notfs, err := NotificationsForUser(db.DefaultContext, user, statuses, 1, 10)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	statuses := []activities_model.NotificationStatus{activities_model.NotificationStatusRead, activities_model.NotificationStatusUnread}
+	notfs, err := activities_model.NotificationsForUser(db.DefaultContext, user, statuses, 1, 10)
 	assert.NoError(t, err)
 	if assert.Len(t, notfs, 3) {
 		assert.EqualValues(t, 5, notfs[0].ID)
@@ -48,7 +49,7 @@ func TestNotificationsForUser(t *testing.T) {
 
 func TestNotification_GetRepo(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	notf := unittest.AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification)
+	notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
 	repo, err := notf.GetRepo()
 	assert.NoError(t, err)
 	assert.Equal(t, repo, notf.Repository)
@@ -57,7 +58,7 @@ func TestNotification_GetRepo(t *testing.T) {
 
 func TestNotification_GetIssue(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	notf := unittest.AssertExistsAndLoadBean(t, &Notification{RepoID: 1}).(*Notification)
+	notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1})
 	issue, err := notf.GetIssue()
 	assert.NoError(t, err)
 	assert.Equal(t, issue, notf.Issue)
@@ -66,46 +67,46 @@ func TestNotification_GetIssue(t *testing.T) {
 
 func TestGetNotificationCount(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	cnt, err := GetNotificationCount(db.DefaultContext, user, NotificationStatusRead)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	cnt, err := activities_model.GetNotificationCount(db.DefaultContext, user, activities_model.NotificationStatusRead)
 	assert.NoError(t, err)
 	assert.EqualValues(t, 0, cnt)
 
-	cnt, err = GetNotificationCount(db.DefaultContext, user, NotificationStatusUnread)
+	cnt, err = activities_model.GetNotificationCount(db.DefaultContext, user, activities_model.NotificationStatusUnread)
 	assert.NoError(t, err)
 	assert.EqualValues(t, 1, cnt)
 }
 
 func TestSetNotificationStatus(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	notf := unittest.AssertExistsAndLoadBean(t,
-		&Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification)
-	_, err := SetNotificationStatus(notf.ID, user, NotificationStatusPinned)
+		&activities_model.Notification{UserID: user.ID, Status: activities_model.NotificationStatusRead})
+	_, err := activities_model.SetNotificationStatus(notf.ID, user, activities_model.NotificationStatusPinned)
 	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t,
-		&Notification{ID: notf.ID, Status: NotificationStatusPinned})
+		&activities_model.Notification{ID: notf.ID, Status: activities_model.NotificationStatusPinned})
 
-	_, err = SetNotificationStatus(1, user, NotificationStatusRead)
+	_, err = activities_model.SetNotificationStatus(1, user, activities_model.NotificationStatusRead)
 	assert.Error(t, err)
-	_, err = SetNotificationStatus(unittest.NonexistentID, user, NotificationStatusRead)
+	_, err = activities_model.SetNotificationStatus(unittest.NonexistentID, user, activities_model.NotificationStatusRead)
 	assert.Error(t, err)
 }
 
 func TestUpdateNotificationStatuses(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	notfUnread := unittest.AssertExistsAndLoadBean(t,
-		&Notification{UserID: user.ID, Status: NotificationStatusUnread}).(*Notification)
+		&activities_model.Notification{UserID: user.ID, Status: activities_model.NotificationStatusUnread})
 	notfRead := unittest.AssertExistsAndLoadBean(t,
-		&Notification{UserID: user.ID, Status: NotificationStatusRead}).(*Notification)
+		&activities_model.Notification{UserID: user.ID, Status: activities_model.NotificationStatusRead})
 	notfPinned := unittest.AssertExistsAndLoadBean(t,
-		&Notification{UserID: user.ID, Status: NotificationStatusPinned}).(*Notification)
-	assert.NoError(t, UpdateNotificationStatuses(user, NotificationStatusUnread, NotificationStatusRead))
+		&activities_model.Notification{UserID: user.ID, Status: activities_model.NotificationStatusPinned})
+	assert.NoError(t, activities_model.UpdateNotificationStatuses(user, activities_model.NotificationStatusUnread, activities_model.NotificationStatusRead))
 	unittest.AssertExistsAndLoadBean(t,
-		&Notification{ID: notfUnread.ID, Status: NotificationStatusRead})
+		&activities_model.Notification{ID: notfUnread.ID, Status: activities_model.NotificationStatusRead})
 	unittest.AssertExistsAndLoadBean(t,
-		&Notification{ID: notfRead.ID, Status: NotificationStatusRead})
+		&activities_model.Notification{ID: notfRead.ID, Status: activities_model.NotificationStatusRead})
 	unittest.AssertExistsAndLoadBean(t,
-		&Notification{ID: notfPinned.ID, Status: NotificationStatusPinned})
+		&activities_model.Notification{ID: notfPinned.ID, Status: activities_model.NotificationStatusPinned})
 }
diff --git a/models/repo_activity.go b/models/activities/repo_activity.go
similarity index 94%
rename from models/repo_activity.go
rename to models/activities/repo_activity.go
index 6a3636ab071e5..4c8aa7e81d1c3 100644
--- a/models/repo_activity.go
+++ b/models/activities/repo_activity.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities
 
 import (
 	"context"
@@ -28,7 +28,7 @@ type ActivityAuthorData struct {
 	Commits    int64  `json:"commits"`
 }
 
-// ActivityStats represets issue and pull request information.
+// ActivityStats represents issue and pull request information.
 type ActivityStats struct {
 	OpenedPRs                   issues_model.PullRequestList
 	OpenedPRAuthorCount         int64
@@ -39,7 +39,7 @@ type ActivityStats struct {
 	ClosedIssues                issues_model.IssueList
 	ClosedIssueAuthorCount      int64
 	UnresolvedIssues            issues_model.IssueList
-	PublishedReleases           []*Release
+	PublishedReleases           []*repo_model.Release
 	PublishedReleaseAuthorCount int64
 	Code                        *git.CodeActivityStats
 }
@@ -49,32 +49,32 @@ func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom
 	stats := &ActivityStats{Code: &git.CodeActivityStats{}}
 	if releases {
 		if err := stats.FillReleases(repo.ID, timeFrom); err != nil {
-			return nil, fmt.Errorf("FillReleases: %v", err)
+			return nil, fmt.Errorf("FillReleases: %w", err)
 		}
 	}
 	if prs {
 		if err := stats.FillPullRequests(repo.ID, timeFrom); err != nil {
-			return nil, fmt.Errorf("FillPullRequests: %v", err)
+			return nil, fmt.Errorf("FillPullRequests: %w", err)
 		}
 	}
 	if issues {
 		if err := stats.FillIssues(repo.ID, timeFrom); err != nil {
-			return nil, fmt.Errorf("FillIssues: %v", err)
+			return nil, fmt.Errorf("FillIssues: %w", err)
 		}
 	}
 	if err := stats.FillUnresolvedIssues(repo.ID, timeFrom, issues, prs); err != nil {
-		return nil, fmt.Errorf("FillUnresolvedIssues: %v", err)
+		return nil, fmt.Errorf("FillUnresolvedIssues: %w", err)
 	}
 	if code {
 		gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath())
 		if err != nil {
-			return nil, fmt.Errorf("OpenRepository: %v", err)
+			return nil, fmt.Errorf("OpenRepository: %w", err)
 		}
 		defer closer.Close()
 
 		code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch)
 		if err != nil {
-			return nil, fmt.Errorf("FillFromGit: %v", err)
+			return nil, fmt.Errorf("FillFromGit: %w", err)
 		}
 		stats.Code = code
 	}
@@ -85,13 +85,13 @@ func GetActivityStats(ctx context.Context, repo *repo_model.Repository, timeFrom
 func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
 	gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.RepoPath())
 	if err != nil {
-		return nil, fmt.Errorf("OpenRepository: %v", err)
+		return nil, fmt.Errorf("OpenRepository: %w", err)
 	}
 	defer closer.Close()
 
 	code, err := gitRepo.GetCodeActivityStats(timeFrom, "")
 	if err != nil {
-		return nil, fmt.Errorf("FillFromGit: %v", err)
+		return nil, fmt.Errorf("FillFromGit: %w", err)
 	}
 	if code.Authors == nil {
 		return nil, nil
@@ -344,7 +344,7 @@ func (stats *ActivityStats) FillReleases(repoID int64, fromTime time.Time) error
 	// Published releases list
 	sess := releasesForActivityStatement(repoID, fromTime)
 	sess.OrderBy("release.created_unix DESC")
-	stats.PublishedReleases = make([]*Release, 0)
+	stats.PublishedReleases = make([]*repo_model.Release, 0)
 	if err = sess.Find(&stats.PublishedReleases); err != nil {
 		return err
 	}
diff --git a/models/statistic.go b/models/activities/statistic.go
similarity index 97%
rename from models/statistic.go
rename to models/activities/statistic.go
index 55ace626c8af5..ea785a3ee2629 100644
--- a/models/statistic.go
+++ b/models/activities/statistic.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package activities
 
 import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -101,7 +101,7 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Oauth = 0
 	stats.Counter.Follow, _ = e.Count(new(user_model.Follow))
 	stats.Counter.Mirror, _ = e.Count(new(repo_model.Mirror))
-	stats.Counter.Release, _ = e.Count(new(Release))
+	stats.Counter.Release, _ = e.Count(new(repo_model.Release))
 	stats.Counter.AuthSource = auth.CountSources()
 	stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook))
 	stats.Counter.Milestone, _ = e.Count(new(issues_model.Milestone))
@@ -111,5 +111,5 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
 	stats.Counter.Project, _ = e.Count(new(project_model.Project))
 	stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
-	return
+	return stats
 }
diff --git a/models/user_heatmap.go b/models/activities/user_heatmap.go
similarity index 97%
rename from models/user_heatmap.go
rename to models/activities/user_heatmap.go
index e908837ae812c..6e76be6c6b589 100644
--- a/models/user_heatmap.go
+++ b/models/activities/user_heatmap.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.package models
 
-package models
+package activities
 
 import (
 	"code.gitea.io/gitea/models/db"
@@ -31,7 +31,7 @@ func GetUserHeatmapDataByUserTeam(user *user_model.User, team *organization.Team
 func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) {
 	hdata := make([]*UserHeatmapData, 0)
 
-	if !activityReadable(user, doer) {
+	if !ActivityReadable(user, doer) {
 		return hdata, nil
 	}
 
diff --git a/models/user_heatmap_test.go b/models/activities/user_heatmap_test.go
similarity index 90%
rename from models/user_heatmap_test.go
rename to models/activities/user_heatmap_test.go
index 9361cb3452fa8..a8a240f790b0f 100644
--- a/models/user_heatmap_test.go
+++ b/models/activities/user_heatmap_test.go
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.package models
 
-package models
+package activities_test
 
 import (
 	"fmt"
 	"testing"
 	"time"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -63,7 +64,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
 	defer timeutil.Unset()
 
 	for _, tc := range testCases {
-		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID}).(*user_model.User)
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: tc.userID})
 
 		doer := &user_model.User{ID: tc.doerID}
 		_, err := unittest.LoadBeanIfExists(doer)
@@ -73,7 +74,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
 		}
 
 		// get the action for comparison
-		actions, err := GetFeeds(db.DefaultContext, GetFeedsOptions{
+		actions, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{
 			RequestedUser:   user,
 			Actor:           doer,
 			IncludePrivate:  true,
@@ -83,7 +84,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
 		assert.NoError(t, err)
 
 		// Get the heatmap and compare
-		heatmap, err := GetUserHeatmapDataByUser(user, doer)
+		heatmap, err := activities_model.GetUserHeatmapDataByUser(user, doer)
 		var contributions int
 		for _, hm := range heatmap {
 			contributions += int(hm.Contributions)
diff --git a/models/admin/notice_test.go b/models/admin/notice_test.go
deleted file mode 100644
index b4613db8e7477..0000000000000
--- a/models/admin/notice_test.go
+++ /dev/null
@@ -1,116 +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
-
-import (
-	"testing"
-
-	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/unittest"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestNotice_TrStr(t *testing.T) {
-	notice := &Notice{
-		Type:        NoticeRepository,
-		Description: "test description",
-	}
-	assert.Equal(t, "admin.notices.type_1", notice.TrStr())
-}
-
-func TestCreateNotice(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	noticeBean := &Notice{
-		Type:        NoticeRepository,
-		Description: "test description",
-	}
-	unittest.AssertNotExistsBean(t, noticeBean)
-	assert.NoError(t, CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description))
-	unittest.AssertExistsAndLoadBean(t, noticeBean)
-}
-
-func TestCreateRepositoryNotice(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	noticeBean := &Notice{
-		Type:        NoticeRepository,
-		Description: "test description",
-	}
-	unittest.AssertNotExistsBean(t, noticeBean)
-	assert.NoError(t, CreateRepositoryNotice(noticeBean.Description))
-	unittest.AssertExistsAndLoadBean(t, noticeBean)
-}
-
-// TODO TestRemoveAllWithNotice
-
-func TestCountNotices(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-	assert.Equal(t, int64(3), CountNotices())
-}
-
-func TestNotices(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	notices, err := 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 = 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, &Notice{ID: 3})
-	assert.NoError(t, DeleteNotice(3))
-	unittest.AssertNotExistsBean(t, &Notice{ID: 3})
-}
-
-func TestDeleteNotices(t *testing.T) {
-	// delete a non-empty range
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 1})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 2})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 3})
-	assert.NoError(t, DeleteNotices(1, 2))
-	unittest.AssertNotExistsBean(t, &Notice{ID: 1})
-	unittest.AssertNotExistsBean(t, &Notice{ID: 2})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 3})
-}
-
-func TestDeleteNotices2(t *testing.T) {
-	// delete an empty range
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 1})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 2})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 3})
-	assert.NoError(t, DeleteNotices(3, 2))
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 1})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 2})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 3})
-}
-
-func TestDeleteNoticesByIDs(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 1})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 2})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 3})
-	assert.NoError(t, DeleteNoticesByIDs([]int64{1, 3}))
-	unittest.AssertNotExistsBean(t, &Notice{ID: 1})
-	unittest.AssertExistsAndLoadBean(t, &Notice{ID: 2})
-	unittest.AssertNotExistsBean(t, &Notice{ID: 3})
-}
diff --git a/models/task.go b/models/admin/task.go
similarity index 96%
rename from models/task.go
rename to models/admin/task.go
index cabb96c60831e..4fa0f10394e67 100644
--- a/models/task.go
+++ b/models/admin/task.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package admin
 
 import (
 	"context"
@@ -156,17 +156,21 @@ type ErrTaskDoesNotExist struct {
 	Type   structs.TaskType
 }
 
-// IsErrTaskDoesNotExist checks if an error is a ErrTaskIsNotExist.
+// IsErrTaskDoesNotExist checks if an error is a ErrTaskDoesNotExist.
 func IsErrTaskDoesNotExist(err error) bool {
 	_, ok := err.(ErrTaskDoesNotExist)
 	return ok
 }
 
 func (err ErrTaskDoesNotExist) Error() string {
-	return fmt.Sprintf("task is not exist [id: %d, repo_id: %d, type: %d]",
+	return fmt.Sprintf("task does not exist [id: %d, repo_id: %d, type: %d]",
 		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{
diff --git a/models/asymkey/error.go b/models/asymkey/error.go
index 5d2be1f289590..3ddeb0498a2d5 100644
--- a/models/asymkey/error.go
+++ b/models/asymkey/error.go
@@ -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
+}
diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go
index 2b99972379c4c..83774533aa7d0 100644
--- a/models/asymkey/gpg_key.go
+++ b/models/asymkey/gpg_key.go
@@ -33,7 +33,7 @@ type GPGKey struct {
 	OwnerID           int64              `xorm:"INDEX NOT NULL"`
 	KeyID             string             `xorm:"INDEX CHAR(16) NOT NULL"`
 	PrimaryKeyID      string             `xorm:"CHAR(16)"`
-	Content           string             `xorm:"TEXT NOT NULL"`
+	Content           string             `xorm:"MEDIUMTEXT NOT NULL"`
 	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
 	ExpiredUnix       timeutil.TimeStamp
 	AddedUnix         timeutil.TimeStamp
@@ -63,6 +63,15 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) {
 	}
 }
 
+// PaddedKeyID show KeyID padded to 16 characters
+func (key *GPGKey) PaddedKeyID() string {
+	if len(key.KeyID) > 15 {
+		return key.KeyID
+	}
+	zeros := "0000000000000000"
+	return zeros[0:16-len(key.KeyID)] + key.KeyID
+}
+
 // ListGPGKeys returns a list of public keys belongs to given user.
 func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) {
 	sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid)
@@ -217,7 +226,7 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
 		if IsErrGPGKeyNotExist(err) {
 			return nil
 		}
-		return fmt.Errorf("GetPublicKeyByID: %v", err)
+		return fmt.Errorf("GetPublicKeyByID: %w", err)
 	}
 
 	// Check if user has access to delete this key.
diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go
index 2f66863091aec..d5b06f83fd97f 100644
--- a/models/asymkey/gpg_key_commit_verification.go
+++ b/models/asymkey/gpg_key_commit_verification.go
@@ -520,5 +520,5 @@ func CalculateTrustStatus(verification *CommitVerification, repoTrustModel repo_
 		}
 	}
 
-	return
+	return err
 }
diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go
index 07bb77bdf468a..2cee45d98f75b 100644
--- a/models/asymkey/gpg_key_test.go
+++ b/models/asymkey/gpg_key_test.go
@@ -196,7 +196,7 @@ Unknown GPG key with good email
 func TestCheckGPGUserEmail(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	testEmailWithUpperCaseLetters := `-----BEGIN PGP PUBLIC KEY BLOCK-----
 Version: GnuPG v1
diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go
index 107a29e985f71..7ed4ad6b3f410 100644
--- a/models/asymkey/ssh_key.go
+++ b/models/asymkey/ssh_key.go
@@ -41,7 +41,7 @@ type PublicKey struct {
 	OwnerID       int64           `xorm:"INDEX NOT NULL"`
 	Name          string          `xorm:"NOT NULL"`
 	Fingerprint   string          `xorm:"INDEX NOT NULL"`
-	Content       string          `xorm:"TEXT NOT NULL"`
+	Content       string          `xorm:"MEDIUMTEXT NOT NULL"`
 	Mode          perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
 	Type          KeyType         `xorm:"NOT NULL DEFAULT 1"`
 	LoginSourceID int64           `xorm:"NOT NULL DEFAULT 0"`
@@ -130,7 +130,7 @@ func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*Pub
 		LoginSourceID: authSourceID,
 	}
 	if err = addKey(ctx, key); err != nil {
-		return nil, fmt.Errorf("addKey: %v", err)
+		return nil, fmt.Errorf("addKey: %w", err)
 	}
 
 	return key, committer.Commit()
diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go
index fd8388e61d5a2..d5c981da47bf1 100644
--- a/models/asymkey/ssh_key_deploy.go
+++ b/models/asymkey/ssh_key_deploy.go
@@ -151,7 +151,7 @@ func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey
 		pkey.Content = content
 		pkey.Name = name
 		if err = addKey(ctx, pkey); err != nil {
-			return nil, fmt.Errorf("addKey: %v", err)
+			return nil, fmt.Errorf("addKey: %w", err)
 		}
 	}
 
diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go
index 747a7e6473c4d..788d58dbabd99 100644
--- a/models/asymkey/ssh_key_fingerprint.go
+++ b/models/asymkey/ssh_key_fingerprint.go
@@ -95,7 +95,7 @@ func CalcFingerprint(publicKeyContent string) (string, error) {
 			log.Info("%s", publicKeyContent)
 			return "", err
 		}
-		return "", fmt.Errorf("%s: %v", fnName, err)
+		return "", fmt.Errorf("%s: %w", fnName, err)
 	}
 	return fp, nil
 }
diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go
index 3f52a4e9e0951..2462310ed977e 100644
--- a/models/asymkey/ssh_key_parse.go
+++ b/models/asymkey/ssh_key_parse.go
@@ -44,7 +44,7 @@ const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
 func extractTypeFromBase64Key(key string) (string, error) {
 	b, err := base64.StdEncoding.DecodeString(key)
 	if err != nil || len(b) < 4 {
-		return "", fmt.Errorf("invalid key format: %v", err)
+		return "", fmt.Errorf("invalid key format: %w", err)
 	}
 
 	keyLength := int(binary.BigEndian.Uint32(b))
@@ -85,7 +85,7 @@ func parseKeyString(content string) (string, error) {
 
 		t, err := extractTypeFromBase64Key(keyContent)
 		if err != nil {
-			return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
+			return "", fmt.Errorf("extractTypeFromBase64Key: %w", err)
 		}
 		keyType = t
 	} else {
@@ -104,14 +104,14 @@ func parseKeyString(content string) (string, error) {
 				var pk rsa.PublicKey
 				_, err2 := asn1.Unmarshal(block.Bytes, &pk)
 				if err2 != nil {
-					return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v", err, err2)
+					return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %w", err, err2)
 				}
 				pub = &pk
 			}
 
 			sshKey, err := ssh.NewPublicKey(pub)
 			if err != nil {
-				return "", fmt.Errorf("unable to convert to ssh public key: %v", err)
+				return "", fmt.Errorf("unable to convert to ssh public key: %w", err)
 			}
 			content = string(ssh.MarshalAuthorizedKey(sshKey))
 		}
@@ -138,7 +138,7 @@ func parseKeyString(content string) (string, error) {
 		// If keyType is not given, extract it from content. If given, validate it.
 		t, err := extractTypeFromBase64Key(keyContent)
 		if err != nil {
-			return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
+			return "", fmt.Errorf("extractTypeFromBase64Key: %w", err)
 		}
 		if len(keyType) == 0 {
 			keyType = t
@@ -149,7 +149,7 @@ func parseKeyString(content string) (string, error) {
 	// Finally we need to check whether we can actually read the proposed key:
 	_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + keyContent + " " + keyComment))
 	if err != nil {
-		return "", fmt.Errorf("invalid ssh public key: %v", err)
+		return "", fmt.Errorf("invalid ssh public key: %w", err)
 	}
 	return keyType + " " + keyContent + " " + keyComment, nil
 }
@@ -191,7 +191,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
 		keyType, length, err = SSHKeyGenParsePublicKey(content)
 	}
 	if err != nil {
-		return "", fmt.Errorf("%s: %v", fnName, err)
+		return "", fmt.Errorf("%s: %w", fnName, err)
 	}
 	log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length)
 
@@ -220,7 +220,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
 		if strings.Contains(err.Error(), "ssh: unknown key algorithm") {
 			return "", 0, ErrKeyUnableVerify{err.Error()}
 		}
-		return "", 0, fmt.Errorf("ParsePublicKey: %v", err)
+		return "", 0, fmt.Errorf("ParsePublicKey: %w", err)
 	}
 
 	// The ssh library can parse the key, so next we find out what key exactly we have.
@@ -267,12 +267,12 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) {
 func writeTmpKeyFile(content string) (string, error) {
 	tmpFile, err := os.CreateTemp(setting.SSH.KeyTestPath, "gitea_keytest")
 	if err != nil {
-		return "", fmt.Errorf("TempFile: %v", err)
+		return "", fmt.Errorf("TempFile: %w", err)
 	}
 	defer tmpFile.Close()
 
 	if _, err = tmpFile.WriteString(content); err != nil {
-		return "", fmt.Errorf("WriteString: %v", err)
+		return "", fmt.Errorf("WriteString: %w", err)
 	}
 	return tmpFile.Name(), nil
 }
@@ -281,7 +281,7 @@ func writeTmpKeyFile(content string) (string, error) {
 func SSHKeyGenParsePublicKey(key string) (string, int, error) {
 	tmpName, err := writeTmpKeyFile(key)
 	if err != nil {
-		return "", 0, fmt.Errorf("writeTmpKeyFile: %v", err)
+		return "", 0, fmt.Errorf("writeTmpKeyFile: %w", err)
 	}
 	defer func() {
 		if err := util.Remove(tmpName); err != nil {
diff --git a/models/asymkey/ssh_key_principals.go b/models/asymkey/ssh_key_principals.go
index 7a5c234f6fd43..e0d407af35677 100644
--- a/models/asymkey/ssh_key_principals.go
+++ b/models/asymkey/ssh_key_principals.go
@@ -51,7 +51,7 @@ func AddPrincipalKey(ownerID int64, content string, authSourceID int64) (*Public
 		LoginSourceID: authSourceID,
 	}
 	if err = db.Insert(ctx, key); err != nil {
-		return nil, fmt.Errorf("addKey: %v", err)
+		return nil, fmt.Errorf("addKey: %w", err)
 	}
 
 	if err = committer.Commit(); err != nil {
diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go
index 71c8860f1cae3..adffedd0b6ea9 100644
--- a/models/asymkey/ssh_key_test.go
+++ b/models/asymkey/ssh_key_test.go
@@ -317,7 +317,7 @@ func TestFromOpenSSH(t *testing.T) {
 			td := t.TempDir()
 
 			data := []byte("hello, ssh world")
-			dataPath := write(t, []byte(data), td, "data")
+			dataPath := write(t, data, td, "data")
 
 			privPath := write(t, []byte(tt.priv), td, "id")
 			write(t, []byte(tt.pub), td, "id.pub")
@@ -372,14 +372,14 @@ func TestToOpenSSH(t *testing.T) {
 			td := t.TempDir()
 
 			data := []byte("hello, ssh world")
-			write(t, []byte(data), td, "data")
+			write(t, data, td, "data")
 
 			armored, err := sshsig.Sign([]byte(tt.priv), bytes.NewReader(data), "file")
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			sigPath := write(t, []byte(armored), td, "oursig")
+			sigPath := write(t, armored, td, "oursig")
 
 			// Create an allowed_signers file with two keys to check against.
 			allowedSigner := "test@rekor.dev " + tt.pub + "\n"
diff --git a/models/auth/main_test.go b/models/auth/main_test.go
index ccbdd4e81c7ca..5d52e963b8677 100644
--- a/models/auth/main_test.go
+++ b/models/auth/main_test.go
@@ -2,24 +2,22 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package auth
+package auth_test
 
 import (
 	"path/filepath"
 	"testing"
 
 	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models"
+	_ "code.gitea.io/gitea/models/activities"
+	_ "code.gitea.io/gitea/models/auth"
+	_ "code.gitea.io/gitea/models/perm/access"
 )
 
 func TestMain(m *testing.M) {
 	unittest.MainTest(m, &unittest.TestOptions{
 		GiteaRootPath: filepath.Join("..", ".."),
-		FixtureFiles: []string{
-			"login_source.yml",
-			"oauth2_application.yml",
-			"oauth2_authorization_code.yml",
-			"oauth2_grant.yml",
-			"webauthn_credential.yml",
-		},
 	})
 }
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index c5c6e91120f85..ccd9336f65180 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -10,6 +10,7 @@ import (
 	"encoding/base32"
 	"encoding/base64"
 	"fmt"
+	"net"
 	"net/url"
 	"strings"
 
@@ -30,9 +31,14 @@ type OAuth2Application struct {
 	Name         string
 	ClientID     string `xorm:"unique"`
 	ClientSecret string
-	RedirectURIs []string           `xorm:"redirect_uris JSON TEXT"`
-	CreatedUnix  timeutil.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix  timeutil.TimeStamp `xorm:"INDEX updated"`
+	// 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"`
 }
 
 func init() {
@@ -56,6 +62,20 @@ 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() != "" {
+			ip := net.ParseIP(uri.Hostname())
+			if ip != nil && ip.IsLoopback() {
+				// strip port
+				uri.Host = uri.Hostname()
+				if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) {
+					return true
+				}
+			}
+		}
+	}
 	return util.IsStringInSlice(redirectURI, app.RedirectURIs, true)
 }
 
@@ -123,7 +143,7 @@ func GetOAuth2ApplicationByClientID(ctx context.Context, clientID string) (app *
 	if !has {
 		return nil, ErrOAuthClientIDInvalid{ClientID: clientID}
 	}
-	return
+	return app, err
 }
 
 // GetOAuth2ApplicationByID returns the oauth2 application with the given id. Returns an error if not found.
@@ -143,24 +163,26 @@ func GetOAuth2ApplicationByID(ctx context.Context, id int64) (app *OAuth2Applica
 func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*OAuth2Application, err error) {
 	apps = make([]*OAuth2Application, 0)
 	err = db.GetEngine(ctx).Where("uid = ?", userID).Find(&apps)
-	return
+	return apps, err
 }
 
 // CreateOAuth2ApplicationOptions holds options to create an oauth2 application
 type CreateOAuth2ApplicationOptions struct {
-	Name         string
-	UserID       int64
-	RedirectURIs []string
+	Name               string
+	UserID             int64
+	ConfidentialClient bool
+	RedirectURIs       []string
 }
 
 // CreateOAuth2Application inserts a new oauth2 application
 func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) {
 	clientID := uuid.New().String()
 	app := &OAuth2Application{
-		UID:          opts.UserID,
-		Name:         opts.Name,
-		ClientID:     clientID,
-		RedirectURIs: opts.RedirectURIs,
+		UID:                opts.UserID,
+		Name:               opts.Name,
+		ClientID:           clientID,
+		RedirectURIs:       opts.RedirectURIs,
+		ConfidentialClient: opts.ConfidentialClient,
 	}
 	if err := db.Insert(ctx, app); err != nil {
 		return nil, err
@@ -170,10 +192,11 @@ func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOp
 
 // UpdateOAuth2ApplicationOptions holds options to update an oauth2 application
 type UpdateOAuth2ApplicationOptions struct {
-	ID           int64
-	Name         string
-	UserID       int64
-	RedirectURIs []string
+	ID                 int64
+	Name               string
+	UserID             int64
+	ConfidentialClient bool
+	RedirectURIs       []string
 }
 
 // UpdateOAuth2Application updates an oauth2 application
@@ -194,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
@@ -204,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
@@ -212,7 +236,8 @@ func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error
 
 func deleteOAuth2Application(ctx context.Context, id, userid int64) error {
 	sess := db.GetEngine(ctx)
-	if deleted, err := sess.Delete(&OAuth2Application{ID: id, UID: userid}); err != nil {
+	// the userid could be 0 if the app is instance-wide
+	if deleted, err := sess.Where(builder.Eq{"id": id, "uid": userid}).Delete(&OAuth2Application{}); err != nil {
 		return err
 	} else if deleted == 0 {
 		return ErrOAuthApplicationNotFound{ID: id}
@@ -300,7 +325,7 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (redirect
 	}
 	q.Set("code", code.Code)
 	redirect.RawQuery = q.Encode()
-	return
+	return redirect, err
 }
 
 // Invalidate deletes the auth code from the database to invalidate this code
@@ -430,7 +455,7 @@ func GetOAuth2GrantByID(ctx context.Context, id int64) (grant *OAuth2Grant, err
 	} else if !has {
 		return nil, nil
 	}
-	return
+	return grant, err
 }
 
 // GetOAuth2GrantsByUserID lists all grants of a certain user
@@ -463,7 +488,7 @@ func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, er
 
 // RevokeOAuth2Grant deletes the grant with grantID and userID
 func RevokeOAuth2Grant(ctx context.Context, grantID, userID int64) error {
-	_, err := db.DeleteByBean(ctx, &OAuth2Grant{ID: grantID, UserID: userID})
+	_, err := db.GetEngine(ctx).Where(builder.Eq{"id": grantID, "user_id": userID}).Delete(&OAuth2Grant{})
 	return err
 }
 
@@ -472,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
@@ -483,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
@@ -499,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)
@@ -512,10 +547,14 @@ func GetActiveOAuth2ProviderSources() ([]*Source, error) {
 func GetActiveOAuth2SourceByName(name string) (*Source, error) {
 	authSource := new(Source)
 	has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
-	if !has || err != nil {
+	if err != nil {
 		return nil, err
 	}
 
+	if !has {
+		return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
+	}
+
 	return authSource, nil
 }
 
@@ -531,7 +570,7 @@ func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error {
 		&OAuth2Application{UID: userID},
 		&OAuth2Grant{UserID: userID},
 	); err != nil {
-		return fmt.Errorf("DeleteBeans: %v", err)
+		return fmt.Errorf("DeleteBeans: %w", err)
 	}
 
 	return nil
diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go
index cb8c4aeb6aa5b..7a4df6b9acd7a 100644
--- a/models/auth/oauth2_test.go
+++ b/models/auth/oauth2_test.go
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package auth
+package auth_test
 
 import (
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unittest"
 
@@ -17,23 +18,23 @@ import (
 
 func TestOAuth2Application_GenerateClientSecret(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
 	secret, err := app.GenerateClientSecret()
 	assert.NoError(t, err)
 	assert.True(t, len(secret) > 0)
-	unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
+	unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret})
 }
 
 func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) {
 	assert.NoError(b, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(b, &OAuth2Application{ID: 1}).(*OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(b, &auth_model.OAuth2Application{ID: 1})
 	for i := 0; i < b.N; i++ {
 		_, _ = app.GenerateClientSecret()
 	}
 }
 
 func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
-	app := &OAuth2Application{
+	app := &auth_model.OAuth2Application{
 		RedirectURIs: []string{"a", "b", "c"},
 	}
 	assert.True(t, app.ContainsRedirectURI("a"))
@@ -42,9 +43,30 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
 	assert.False(t, app.ContainsRedirectURI("d"))
 }
 
+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
+	// https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1:3456/"))
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
+	assert.True(t, app.ContainsRedirectURI("http://[::1]:3456/"))
+
+	// not http
+	assert.False(t, app.ContainsRedirectURI("https://127.0.0.1:3456/"))
+	// not loopback
+	assert.False(t, app.ContainsRedirectURI("http://192.168.0.1:9954/"))
+	assert.False(t, app.ContainsRedirectURI("http://intranet:3456/"))
+	// unparseable
+	assert.False(t, app.ContainsRedirectURI(":"))
+}
+
 func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
 	secret, err := app.GenerateClientSecret()
 	assert.NoError(t, err)
 	assert.True(t, app.ValidateClientSecret([]byte(secret)))
@@ -53,31 +75,31 @@ func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
 
 func TestGetOAuth2ApplicationByClientID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app, err := GetOAuth2ApplicationByClientID(db.DefaultContext, "da7da3ba-9a13-4167-856f-3899de0b0138")
+	app, err := auth_model.GetOAuth2ApplicationByClientID(db.DefaultContext, "da7da3ba-9a13-4167-856f-3899de0b0138")
 	assert.NoError(t, err)
 	assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID)
 
-	app, err = GetOAuth2ApplicationByClientID(db.DefaultContext, "invalid client id")
+	app, err = auth_model.GetOAuth2ApplicationByClientID(db.DefaultContext, "invalid client id")
 	assert.Error(t, err)
 	assert.Nil(t, app)
 }
 
 func TestCreateOAuth2Application(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app, err := CreateOAuth2Application(db.DefaultContext, CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
+	app, err := auth_model.CreateOAuth2Application(db.DefaultContext, auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1})
 	assert.NoError(t, err)
 	assert.Equal(t, "newapp", app.Name)
 	assert.Len(t, app.ClientID, 36)
-	unittest.AssertExistsAndLoadBean(t, &OAuth2Application{Name: "newapp"})
+	unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"})
 }
 
 func TestOAuth2Application_TableName(t *testing.T) {
-	assert.Equal(t, "oauth2_application", new(OAuth2Application).TableName())
+	assert.Equal(t, "oauth2_application", new(auth_model.OAuth2Application).TableName())
 }
 
 func TestOAuth2Application_GetGrantByUserID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
 	grant, err := app.GetGrantByUserID(db.DefaultContext, 1)
 	assert.NoError(t, err)
 	assert.Equal(t, int64(1), grant.UserID)
@@ -89,7 +111,7 @@ func TestOAuth2Application_GetGrantByUserID(t *testing.T) {
 
 func TestOAuth2Application_CreateGrant(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(t, &OAuth2Application{ID: 1}).(*OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})
 	grant, err := app.CreateGrant(db.DefaultContext, 2, "")
 	assert.NoError(t, err)
 	assert.NotNil(t, grant)
@@ -102,26 +124,26 @@ func TestOAuth2Application_CreateGrant(t *testing.T) {
 
 func TestGetOAuth2GrantByID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	grant, err := GetOAuth2GrantByID(db.DefaultContext, 1)
+	grant, err := auth_model.GetOAuth2GrantByID(db.DefaultContext, 1)
 	assert.NoError(t, err)
 	assert.Equal(t, int64(1), grant.ID)
 
-	grant, err = GetOAuth2GrantByID(db.DefaultContext, 34923458)
+	grant, err = auth_model.GetOAuth2GrantByID(db.DefaultContext, 34923458)
 	assert.NoError(t, err)
 	assert.Nil(t, grant)
 }
 
 func TestOAuth2Grant_IncreaseCounter(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	grant := unittest.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 1}).(*OAuth2Grant)
+	grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1})
 	assert.NoError(t, grant.IncreaseCounter(db.DefaultContext))
 	assert.Equal(t, int64(2), grant.Counter)
-	unittest.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Counter: 2})
+	unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2})
 }
 
 func TestOAuth2Grant_ScopeContains(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	grant := unittest.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1, Scope: "openid profile"}).(*OAuth2Grant)
+	grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"})
 	assert.True(t, grant.ScopeContains("openid"))
 	assert.True(t, grant.ScopeContains("profile"))
 	assert.False(t, grant.ScopeContains("profil"))
@@ -130,7 +152,7 @@ func TestOAuth2Grant_ScopeContains(t *testing.T) {
 
 func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	grant := unittest.AssertExistsAndLoadBean(t, &OAuth2Grant{ID: 1}).(*OAuth2Grant)
+	grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1})
 	code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256")
 	assert.NoError(t, err)
 	assert.NotNil(t, code)
@@ -138,46 +160,46 @@ func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) {
 }
 
 func TestOAuth2Grant_TableName(t *testing.T) {
-	assert.Equal(t, "oauth2_grant", new(OAuth2Grant).TableName())
+	assert.Equal(t, "oauth2_grant", new(auth_model.OAuth2Grant).TableName())
 }
 
 func TestGetOAuth2GrantsByUserID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	result, err := GetOAuth2GrantsByUserID(db.DefaultContext, 1)
+	result, err := auth_model.GetOAuth2GrantsByUserID(db.DefaultContext, 1)
 	assert.NoError(t, err)
 	assert.Len(t, result, 1)
 	assert.Equal(t, int64(1), result[0].ID)
 	assert.Equal(t, result[0].ApplicationID, result[0].Application.ID)
 
-	result, err = GetOAuth2GrantsByUserID(db.DefaultContext, 34134)
+	result, err = auth_model.GetOAuth2GrantsByUserID(db.DefaultContext, 34134)
 	assert.NoError(t, err)
 	assert.Empty(t, result)
 }
 
 func TestRevokeOAuth2Grant(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	assert.NoError(t, RevokeOAuth2Grant(db.DefaultContext, 1, 1))
-	unittest.AssertNotExistsBean(t, &OAuth2Grant{ID: 1, UserID: 1})
+	assert.NoError(t, auth_model.RevokeOAuth2Grant(db.DefaultContext, 1, 1))
+	unittest.AssertNotExistsBean(t, &auth_model.OAuth2Grant{ID: 1, UserID: 1})
 }
 
 //////////////////// Authorization Code
 
 func TestGetOAuth2AuthorizationByCode(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	code, err := GetOAuth2AuthorizationByCode(db.DefaultContext, "authcode")
+	code, err := auth_model.GetOAuth2AuthorizationByCode(db.DefaultContext, "authcode")
 	assert.NoError(t, err)
 	assert.NotNil(t, code)
 	assert.Equal(t, "authcode", code.Code)
 	assert.Equal(t, int64(1), code.ID)
 
-	code, err = GetOAuth2AuthorizationByCode(db.DefaultContext, "does not exist")
+	code, err = auth_model.GetOAuth2AuthorizationByCode(db.DefaultContext, "does not exist")
 	assert.NoError(t, err)
 	assert.Nil(t, code)
 }
 
 func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
 	// test plain
-	code := &OAuth2AuthorizationCode{
+	code := &auth_model.OAuth2AuthorizationCode{
 		CodeChallengeMethod: "plain",
 		CodeChallenge:       "test123",
 	}
@@ -185,7 +207,7 @@ func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
 	assert.False(t, code.ValidateCodeChallenge("ierwgjoergjio"))
 
 	// test S256
-	code = &OAuth2AuthorizationCode{
+	code = &auth_model.OAuth2AuthorizationCode{
 		CodeChallengeMethod: "S256",
 		CodeChallenge:       "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg",
 	}
@@ -193,14 +215,14 @@ func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
 	assert.False(t, code.ValidateCodeChallenge("wiogjerogorewngoenrgoiuenorg"))
 
 	// test unknown
-	code = &OAuth2AuthorizationCode{
+	code = &auth_model.OAuth2AuthorizationCode{
 		CodeChallengeMethod: "monkey",
 		CodeChallenge:       "foiwgjioriogeiogjerger",
 	}
 	assert.False(t, code.ValidateCodeChallenge("foiwgjioriogeiogjerger"))
 
 	// test no code challenge
-	code = &OAuth2AuthorizationCode{
+	code = &auth_model.OAuth2AuthorizationCode{
 		CodeChallengeMethod: "",
 		CodeChallenge:       "foierjiogerogerg",
 	}
@@ -208,7 +230,7 @@ func TestOAuth2AuthorizationCode_ValidateCodeChallenge(t *testing.T) {
 }
 
 func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) {
-	code := &OAuth2AuthorizationCode{
+	code := &auth_model.OAuth2AuthorizationCode{
 		RedirectURI: "https://example.com/callback",
 		Code:        "thecode",
 	}
@@ -224,11 +246,11 @@ func TestOAuth2AuthorizationCode_GenerateRedirectURI(t *testing.T) {
 
 func TestOAuth2AuthorizationCode_Invalidate(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	code := unittest.AssertExistsAndLoadBean(t, &OAuth2AuthorizationCode{Code: "authcode"}).(*OAuth2AuthorizationCode)
+	code := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
 	assert.NoError(t, code.Invalidate(db.DefaultContext))
-	unittest.AssertNotExistsBean(t, &OAuth2AuthorizationCode{Code: "authcode"})
+	unittest.AssertNotExistsBean(t, &auth_model.OAuth2AuthorizationCode{Code: "authcode"})
 }
 
 func TestOAuth2AuthorizationCode_TableName(t *testing.T) {
-	assert.Equal(t, "oauth2_authorization_code", new(OAuth2AuthorizationCode).TableName())
+	assert.Equal(t, "oauth2_authorization_code", new(auth_model.OAuth2AuthorizationCode).TableName())
 }
diff --git a/models/auth/source.go b/models/auth/source.go
index 6f4f5addcb9c6..f8be5398aef67 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -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
diff --git a/models/auth/source_test.go b/models/auth/source_test.go
index 6a8e286910a2d..67e96ee19efd8 100644
--- a/models/auth/source_test.go
+++ b/models/auth/source_test.go
@@ -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 auth
+package auth_test
 
 import (
 	"strings"
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/json"
@@ -37,13 +38,13 @@ func (source *TestSource) ToDB() ([]byte, error) {
 func TestDumpAuthSource(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	authSourceSchema, err := db.TableInfo(new(Source))
+	authSourceSchema, err := db.TableInfo(new(auth_model.Source))
 	assert.NoError(t, err)
 
-	RegisterTypeConfig(OAuth2, new(TestSource))
+	auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource))
 
-	CreateSource(&Source{
-		Type:     OAuth2,
+	auth_model.CreateSource(&auth_model.Source{
+		Type:     auth_model.OAuth2,
 		Name:     "TestSource",
 		IsActive: false,
 		Cfg: &TestSource{
diff --git a/models/token.go b/models/auth/token.go
similarity index 83%
rename from models/token.go
rename to models/auth/token.go
index b89514309c492..17c07531f85ad 100644
--- a/models/token.go
+++ b/models/auth/token.go
@@ -3,14 +3,13 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package auth
 
 import (
 	"crypto/subtle"
 	"fmt"
 	"time"
 
-	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
@@ -21,6 +20,42 @@ import (
 	lru "github.com/hashicorp/golang-lru"
 )
 
+// ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
+type ErrAccessTokenNotExist struct {
+	Token string
+}
+
+// IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
+func IsErrAccessTokenNotExist(err error) bool {
+	_, ok := err.(ErrAccessTokenNotExist)
+	return ok
+}
+
+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{}
+
+// IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
+func IsErrAccessTokenEmpty(err error) bool {
+	_, ok := err.(ErrAccessTokenEmpty)
+	return ok
+}
+
+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.
@@ -51,7 +86,7 @@ func init() {
 			var err error
 			successfulAccessTokenCache, err = lru.New(setting.SuccessfulTokensCacheSize)
 			if err != nil {
-				return fmt.Errorf("unable to allocate AccessToken cache: %v", err)
+				return fmt.Errorf("unable to allocate AccessToken cache: %w", err)
 			}
 		} else {
 			successfulAccessTokenCache = nil
@@ -68,7 +103,7 @@ func NewAccessToken(t *AccessToken) error {
 	}
 	t.TokenSalt = salt
 	t.Token = base.EncodeSha1(gouuid.New().String())
-	t.TokenHash = auth.HashToken(t.Token, t.TokenSalt)
+	t.TokenHash = HashToken(t.Token, t.TokenSalt)
 	t.TokenLastEight = t.Token[len(t.Token)-8:]
 	_, err = db.GetEngine(db.DefaultContext).Insert(t)
 	return err
@@ -130,7 +165,7 @@ func GetAccessTokenBySHA(token string) (*AccessToken, error) {
 	}
 
 	for _, t := range tokens {
-		tempHash := auth.HashToken(token, t.TokenSalt)
+		tempHash := HashToken(token, t.TokenSalt)
 		if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
 			if successfulAccessTokenCache != nil {
 				successfulAccessTokenCache.Add(token, t.ID)
diff --git a/models/token_test.go b/models/auth/token_test.go
similarity index 62%
rename from models/token_test.go
rename to models/auth/token_test.go
index 007148870a1ad..b27ff13406cc2 100644
--- a/models/token_test.go
+++ b/models/auth/token_test.go
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package auth_test
 
 import (
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/unittest"
 
 	"github.com/stretchr/testify/assert"
@@ -14,77 +15,77 @@ import (
 
 func TestNewAccessToken(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	token := &AccessToken{
+	token := &auth_model.AccessToken{
 		UID:  3,
 		Name: "Token C",
 	}
-	assert.NoError(t, NewAccessToken(token))
+	assert.NoError(t, auth_model.NewAccessToken(token))
 	unittest.AssertExistsAndLoadBean(t, token)
 
-	invalidToken := &AccessToken{
+	invalidToken := &auth_model.AccessToken{
 		ID:   token.ID, // duplicate
 		UID:  2,
 		Name: "Token F",
 	}
-	assert.Error(t, NewAccessToken(invalidToken))
+	assert.Error(t, auth_model.NewAccessToken(invalidToken))
 }
 
 func TestAccessTokenByNameExists(t *testing.T) {
 	name := "Token Gitea"
 
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	token := &AccessToken{
+	token := &auth_model.AccessToken{
 		UID:  3,
 		Name: name,
 	}
 
 	// Check to make sure it doesn't exists already
-	exist, err := AccessTokenByNameExists(token)
+	exist, err := auth_model.AccessTokenByNameExists(token)
 	assert.NoError(t, err)
 	assert.False(t, exist)
 
 	// Save it to the database
-	assert.NoError(t, NewAccessToken(token))
+	assert.NoError(t, auth_model.NewAccessToken(token))
 	unittest.AssertExistsAndLoadBean(t, token)
 
 	// This token must be found by name in the DB now
-	exist, err = AccessTokenByNameExists(token)
+	exist, err = auth_model.AccessTokenByNameExists(token)
 	assert.NoError(t, err)
 	assert.True(t, exist)
 
-	user4Token := &AccessToken{
+	user4Token := &auth_model.AccessToken{
 		UID:  4,
 		Name: name,
 	}
 
 	// Name matches but different user ID, this shouldn't exists in the
 	// database
-	exist, err = AccessTokenByNameExists(user4Token)
+	exist, err = auth_model.AccessTokenByNameExists(user4Token)
 	assert.NoError(t, err)
 	assert.False(t, exist)
 }
 
 func TestGetAccessTokenBySHA(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	token, err := GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36")
+	token, err := auth_model.GetAccessTokenBySHA("d2c6c1ba3890b309189a8e618c72a162e4efbf36")
 	assert.NoError(t, err)
 	assert.Equal(t, int64(1), token.UID)
 	assert.Equal(t, "Token A", token.Name)
 	assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash)
 	assert.Equal(t, "e4efbf36", token.TokenLastEight)
 
-	_, err = GetAccessTokenBySHA("notahash")
+	_, err = auth_model.GetAccessTokenBySHA("notahash")
 	assert.Error(t, err)
-	assert.True(t, IsErrAccessTokenNotExist(err))
+	assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
 
-	_, err = GetAccessTokenBySHA("")
+	_, err = auth_model.GetAccessTokenBySHA("")
 	assert.Error(t, err)
-	assert.True(t, IsErrAccessTokenEmpty(err))
+	assert.True(t, auth_model.IsErrAccessTokenEmpty(err))
 }
 
 func TestListAccessTokens(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	tokens, err := ListAccessTokens(ListAccessTokensOptions{UserID: 1})
+	tokens, err := auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 1})
 	assert.NoError(t, err)
 	if assert.Len(t, tokens, 2) {
 		assert.Equal(t, int64(1), tokens[0].UID)
@@ -93,39 +94,39 @@ func TestListAccessTokens(t *testing.T) {
 		assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B")
 	}
 
-	tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 2})
+	tokens, err = auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 2})
 	assert.NoError(t, err)
 	if assert.Len(t, tokens, 1) {
 		assert.Equal(t, int64(2), tokens[0].UID)
 		assert.Equal(t, "Token A", tokens[0].Name)
 	}
 
-	tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 100})
+	tokens, err = auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: 100})
 	assert.NoError(t, err)
 	assert.Empty(t, tokens)
 }
 
 func TestUpdateAccessToken(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
+	token, err := auth_model.GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
 	assert.NoError(t, err)
 	token.Name = "Token Z"
 
-	assert.NoError(t, UpdateAccessToken(token))
+	assert.NoError(t, auth_model.UpdateAccessToken(token))
 	unittest.AssertExistsAndLoadBean(t, token)
 }
 
 func TestDeleteAccessTokenByID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	token, err := GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
+	token, err := auth_model.GetAccessTokenBySHA("4c6f36e6cf498e2a448662f915d932c09c5a146c")
 	assert.NoError(t, err)
 	assert.Equal(t, int64(1), token.UID)
 
-	assert.NoError(t, DeleteAccessTokenByID(token.ID, 1))
+	assert.NoError(t, auth_model.DeleteAccessTokenByID(token.ID, 1))
 	unittest.AssertNotExistsBean(t, token)
 
-	err = DeleteAccessTokenByID(100, 100)
+	err = auth_model.DeleteAccessTokenByID(100, 100)
 	assert.Error(t, err)
-	assert.True(t, IsErrAccessTokenNotExist(err))
+	assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
 }
diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go
index c5bd972f91641..736d4c340c404 100644
--- a/models/auth/twofactor.go
+++ b/models/auth/twofactor.go
@@ -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"`
diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go
index 2dc3043780101..1575b6cbab678 100644
--- a/models/auth/webauthn.go
+++ b/models/auth/webauthn.go
@@ -6,12 +6,12 @@ package auth
 
 import (
 	"context"
-	"encoding/base32"
 	"fmt"
 	"strings"
 
 	"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"
@@ -20,14 +20,19 @@ import (
 // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
 type ErrWebAuthnCredentialNotExist struct {
 	ID           int64
-	CredentialID string
+	CredentialID []byte
 }
 
 func (err ErrWebAuthnCredentialNotExist) Error() string {
-	if err.CredentialID == "" {
+	if len(err.CredentialID) == 0 {
 		return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID)
 	}
-	return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %s]", err.CredentialID)
+	return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
+}
+
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
+	return util.ErrNotExist
 }
 
 // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
@@ -43,7 +48,7 @@ type WebAuthnCredential struct {
 	Name            string
 	LowerName       string `xorm:"unique(s)"`
 	UserID          int64  `xorm:"INDEX unique(s)"`
-	CredentialID    string `xorm:"INDEX VARCHAR(410)"`
+	CredentialID    []byte `xorm:"INDEX VARBINARY(1024)"`
 	PublicKey       []byte
 	AttestationType string
 	AAGUID          []byte
@@ -94,9 +99,8 @@ type WebAuthnCredentialList []*WebAuthnCredential
 func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
 	creds := make([]webauthn.Credential, 0, len(list))
 	for _, cred := range list {
-		credID, _ := base32.HexEncoding.DecodeString(cred.CredentialID)
 		creds = append(creds, webauthn.Credential{
-			ID:              credID,
+			ID:              cred.CredentialID,
 			PublicKey:       cred.PublicKey,
 			AttestationType: cred.AttestationType,
 			Authenticator: webauthn.Authenticator{
@@ -164,11 +168,11 @@ func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) {
 }
 
 // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
-func GetWebAuthnCredentialByCredID(userID int64, credID string) (*WebAuthnCredential, error) {
+func GetWebAuthnCredentialByCredID(userID int64, credID []byte) (*WebAuthnCredential, error) {
 	return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID)
 }
 
-func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID string) (*WebAuthnCredential, error) {
+func getWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) {
 	cred := new(WebAuthnCredential)
 	if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
 		return nil, err
@@ -187,7 +191,7 @@ func createCredential(ctx context.Context, userID int64, name string, cred *weba
 	c := &WebAuthnCredential{
 		UserID:          userID,
 		Name:            name,
-		CredentialID:    base32.HexEncoding.EncodeToString(cred.ID),
+		CredentialID:    cred.ID,
 		PublicKey:       cred.PublicKey,
 		AttestationType: cred.AttestationType,
 		AAGUID:          cred.Authenticator.AAGUID,
diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go
index 216bf110806eb..29344376cc41b 100644
--- a/models/auth/webauthn_test.go
+++ b/models/auth/webauthn_test.go
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package auth
+package auth_test
 
 import (
-	"encoding/base32"
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/unittest"
 
 	"github.com/duo-labs/webauthn/webauthn"
@@ -17,53 +17,51 @@ import (
 func TestGetWebAuthnCredentialByID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	res, err := GetWebAuthnCredentialByID(1)
+	res, err := auth_model.GetWebAuthnCredentialByID(1)
 	assert.NoError(t, err)
 	assert.Equal(t, "WebAuthn credential", res.Name)
 
-	_, err = GetWebAuthnCredentialByID(342432)
+	_, err = auth_model.GetWebAuthnCredentialByID(342432)
 	assert.Error(t, err)
-	assert.True(t, IsErrWebAuthnCredentialNotExist(err))
+	assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err))
 }
 
 func TestGetWebAuthnCredentialsByUID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	res, err := GetWebAuthnCredentialsByUID(32)
+	res, err := auth_model.GetWebAuthnCredentialsByUID(32)
 	assert.NoError(t, err)
 	assert.Len(t, res, 1)
 	assert.Equal(t, "WebAuthn credential", res[0].Name)
 }
 
 func TestWebAuthnCredential_TableName(t *testing.T) {
-	assert.Equal(t, "webauthn_credential", WebAuthnCredential{}.TableName())
+	assert.Equal(t, "webauthn_credential", auth_model.WebAuthnCredential{}.TableName())
 }
 
 func TestWebAuthnCredential_UpdateSignCount(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	cred := unittest.AssertExistsAndLoadBean(t, &WebAuthnCredential{ID: 1}).(*WebAuthnCredential)
+	cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
 	cred.SignCount = 1
 	assert.NoError(t, cred.UpdateSignCount())
-	unittest.AssertExistsIf(t, true, &WebAuthnCredential{ID: 1, SignCount: 1})
+	unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1})
 }
 
 func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	cred := unittest.AssertExistsAndLoadBean(t, &WebAuthnCredential{ID: 1}).(*WebAuthnCredential)
+	cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1})
 	cred.SignCount = 0xffffffff
 	assert.NoError(t, cred.UpdateSignCount())
-	unittest.AssertExistsIf(t, true, &WebAuthnCredential{ID: 1, SignCount: 0xffffffff})
+	unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff})
 }
 
 func TestCreateCredential(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	res, err := CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
+	res, err := auth_model.CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
 	assert.NoError(t, err)
 	assert.Equal(t, "WebAuthn Created Credential", res.Name)
-	bs, err := base32.HexEncoding.DecodeString(res.CredentialID)
-	assert.NoError(t, err)
-	assert.Equal(t, []byte("Test"), bs)
+	assert.Equal(t, []byte("Test"), res.CredentialID)
 
-	unittest.AssertExistsIf(t, true, &WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
+	unittest.AssertExistsIf(t, true, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
 }
diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go
index 9f7b0c474ff05..30bcad033c125 100644
--- a/models/avatars/avatar.go
+++ b/models/avatars/avatar.go
@@ -13,14 +13,19 @@ 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"
 	"code.gitea.io/gitea/modules/setting"
 )
 
-// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
-const DefaultAvatarPixelSize = 28
+const (
+	// DefaultAvatarClass is the default class of a rendered avatar
+	DefaultAvatarClass = "ui avatar vm"
+	// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
+	DefaultAvatarPixelSize = 28
+)
 
 // EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
 type EmailHash struct {
@@ -72,7 +77,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 +154,11 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
 		return DefaultAvatarLink()
 	}
 
+	enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
+	enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
+
 	var err error
-	if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
+	if enableFederatedAvatar && system_model.LibravatarService != nil {
 		emailHash := saveEmailHash(email)
 		if final {
 			// for final link, we can spend more time on slow external query
@@ -166,12 +174,18 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
 			urlStr += "?size=" + strconv.Itoa(size)
 		}
 		return urlStr
-	} else if !setting.DisableGravatar {
+	}
+
+	disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
+
+	disableGravatar := disableGravatarSetting.GetValueBool()
+	if !disableGravatar {
 		// 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()
 }
 
diff --git a/models/avatars/avatar_test.go b/models/avatars/avatar_test.go
index 4d6255ca5fefb..ace5445fc0ed8 100644
--- a/models/avatars/avatar_test.go
+++ b/models/avatars/avatar_test.go
@@ -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),
 	)
 }
diff --git a/models/avatars/main_test.go b/models/avatars/main_test.go
new file mode 100644
index 0000000000000..0e98d8f64d8c1
--- /dev/null
+++ b/models/avatars/main_test.go
@@ -0,0 +1,22 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package avatars_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models"
+	_ "code.gitea.io/gitea/models/activities"
+	_ "code.gitea.io/gitea/models/perm/access"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", ".."),
+	})
+}
diff --git a/models/consistency.go b/models/consistency.go
deleted file mode 100644
index 18ed9195fce7b..0000000000000
--- a/models/consistency.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-	"code.gitea.io/gitea/models/db"
-	repo_model "code.gitea.io/gitea/models/repo"
-	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/setting"
-
-	"xorm.io/builder"
-)
-
-// CountNullArchivedRepository counts the number of repositories with is_archived is null
-func CountNullArchivedRepository() (int64, error) {
-	return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Count(new(repo_model.Repository))
-}
-
-// FixNullArchivedRepository sets is_archived to false where it is null
-func FixNullArchivedRepository() (int64, error) {
-	return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&repo_model.Repository{
-		IsArchived: false,
-	})
-}
-
-// CountWrongUserType count OrgUser who have wrong type
-func CountWrongUserType() (int64, error) {
-	return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(user_model.User))
-}
-
-// FixWrongUserType fix OrgUser who have wrong type
-func FixWrongUserType() (int64, error) {
-	return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&user_model.User{Type: 1})
-}
-
-// CountActionCreatedUnixString count actions where created_unix is an empty string
-func CountActionCreatedUnixString() (int64, error) {
-	if setting.Database.UseSQLite3 {
-		return db.GetEngine(db.DefaultContext).Where(`created_unix = ""`).Count(new(Action))
-	}
-	return 0, nil
-}
-
-// FixActionCreatedUnixString set created_unix to zero if it is an empty string
-func FixActionCreatedUnixString() (int64, error) {
-	if setting.Database.UseSQLite3 {
-		res, err := db.GetEngine(db.DefaultContext).Exec(`UPDATE action SET created_unix = 0 WHERE created_unix = ""`)
-		if err != nil {
-			return 0, err
-		}
-		return res.RowsAffected()
-	}
-	return 0, nil
-}
diff --git a/models/db/common.go b/models/db/common.go
new file mode 100644
index 0000000000000..1a59a8b5c697f
--- /dev/null
+++ b/models/db/common.go
@@ -0,0 +1,23 @@
+// 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
+
+import (
+	"strings"
+
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+
+	"xorm.io/builder"
+)
+
+// BuildCaseInsensitiveLike returns a condition to check if the given value is like the given key case-insensitively.
+// Handles especially SQLite correctly as UPPER there only transforms ASCII letters.
+func BuildCaseInsensitiveLike(key, value string) builder.Cond {
+	if setting.Database.UseSQLite3 {
+		return builder.Like{"UPPER(" + key + ")", util.ToUpperASCII(value)}
+	}
+	return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
+}
diff --git a/models/db/context.go b/models/db/context.go
index c41d761802b52..4fd35200cf71c 100644
--- a/models/db/context.go
+++ b/models/db/context.go
@@ -23,23 +23,30 @@ type contextKey struct {
 	name string
 }
 
-// EnginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
-var EnginedContextKey = &contextKey{"engined"}
+// enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
+var enginedContextKey = &contextKey{"engined"}
+var _ Engined = &Context{}
 
 // Context represents a db context
 type Context struct {
 	context.Context
-	e Engine
+	e           Engine
+	transaction bool
 }
 
-// WithEngine returns a Context from a context.Context and Engine
-func WithEngine(ctx context.Context, e Engine) *Context {
+func newContext(ctx context.Context, e Engine, transaction bool) *Context {
 	return &Context{
-		Context: ctx,
-		e:       e.Context(ctx),
+		Context:     ctx,
+		e:           e,
+		transaction: transaction,
 	}
 }
 
+// InTransaction if context is in a transaction
+func (ctx *Context) InTransaction() bool {
+	return ctx.transaction
+}
+
 // Engine returns db engine
 func (ctx *Context) Engine() Engine {
 	return ctx.e
@@ -47,7 +54,7 @@ func (ctx *Context) Engine() Engine {
 
 // Value shadows Value for context.Context but allows us to get ourselves and an Engined object
 func (ctx *Context) Value(key interface{}) interface{} {
-	if key == EnginedContextKey {
+	if key == enginedContextKey {
 		return ctx
 	}
 	return ctx.Context.Value(key)
@@ -55,7 +62,7 @@ func (ctx *Context) Value(key interface{}) interface{} {
 
 // WithContext returns this engine tied to this context
 func (ctx *Context) WithContext(other context.Context) *Context {
-	return WithEngine(other, ctx.e)
+	return newContext(ctx, ctx.e.Context(other), ctx.transaction)
 }
 
 // Engined structs provide an Engine
@@ -68,7 +75,7 @@ func GetEngine(ctx context.Context) Engine {
 	if engined, ok := ctx.(Engined); ok {
 		return engined.Engine()
 	}
-	enginedInterface := ctx.Value(EnginedContextKey)
+	enginedInterface := ctx.Value(enginedContextKey)
 	if enginedInterface != nil {
 		return enginedInterface.(Engined).Engine()
 	}
@@ -89,22 +96,11 @@ func TxContext() (*Context, Committer, error) {
 		return nil, nil, err
 	}
 
-	return &Context{
-		Context: DefaultContext,
-		e:       sess,
-	}, sess, nil
-}
-
-// WithContext represents executing database operations
-func WithContext(f func(ctx *Context) error) error {
-	return f(&Context{
-		Context: DefaultContext,
-		e:       x,
-	})
+	return newContext(DefaultContext, sess, true), sess, nil
 }
 
 // WithTx represents executing database operations on a transaction
-// you can optionally change the context to a parrent one
+// you can optionally change the context to a parent one
 func WithTx(f func(ctx context.Context) error, stdCtx ...context.Context) error {
 	parentCtx := DefaultContext
 	if len(stdCtx) != 0 && stdCtx[0] != nil {
@@ -118,10 +114,7 @@ func WithTx(f func(ctx context.Context) error, stdCtx ...context.Context) error
 		return err
 	}
 
-	if err := f(&Context{
-		Context: parentCtx,
-		e:       sess,
-	}); err != nil {
+	if err := f(newContext(parentCtx, sess, true)); err != nil {
 		return err
 	}
 
diff --git a/models/db/engine.go b/models/db/engine.go
index 8a3b4b206e485..41949eb6f60f8 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -130,7 +130,7 @@ func SyncAllTables() error {
 func InitEngine(ctx context.Context) error {
 	xormEngine, err := newXORMEngine()
 	if err != nil {
-		return fmt.Errorf("failed to connect to database: %v", err)
+		return fmt.Errorf("failed to connect to database: %w", err)
 	}
 
 	xormEngine.SetMapper(names.GonicMapper{})
@@ -189,16 +189,16 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine)
 	// However, we should think carefully about should we support re-install on an installed instance,
 	// as there may be other problems due to secret reinitialization.
 	if err = migrateFunc(x); err != nil {
-		return fmt.Errorf("migrate: %v", err)
+		return fmt.Errorf("migrate: %w", err)
 	}
 
 	if err = SyncAllTables(); err != nil {
-		return fmt.Errorf("sync database struct error: %v", err)
+		return fmt.Errorf("sync database struct error: %w", err)
 	}
 
 	for _, initFunc := range initFuncs {
 		if err := initFunc(); err != nil {
-			return fmt.Errorf("initFunc failed: %v", err)
+			return fmt.Errorf("initFunc failed: %w", err)
 		}
 	}
 
@@ -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)
@@ -285,5 +285,14 @@ func DeleteAllRecords(tableName string) error {
 // GetMaxID will return max id of the table
 func GetMaxID(beanOrTableName interface{}) (maxID int64, err error) {
 	_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
-	return
+	return maxID, err
+}
+
+func SetLogSQL(ctx context.Context, on bool) {
+	e := GetEngine(ctx)
+	if x, ok := e.(*xorm.Engine); ok {
+		x.ShowSQL(on)
+	} else if sess, ok := e.(*xorm.Session); ok {
+		sess.Engine().ShowSQL(on)
+	}
 }
diff --git a/models/db/engine_test.go b/models/db/engine_test.go
index 41279c5005f84..c26d94c3405e7 100644
--- a/models/db/engine_test.go
+++ b/models/db/engine_test.go
@@ -5,7 +5,6 @@
 package db_test
 
 import (
-	"os"
 	"path/filepath"
 	"testing"
 
@@ -20,8 +19,7 @@ import (
 func TestDumpDatabase(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	dir, err := os.MkdirTemp(os.TempDir(), "dump")
-	assert.NoError(t, err)
+	dir := t.TempDir()
 
 	type Version struct {
 		ID      int64 `xorm:"pk autoincr"`
diff --git a/models/db/error.go b/models/db/error.go
index 65572299436a6..9577fa55dbb38 100644
--- a/models/db/error.go
+++ b/models/db/error.go
@@ -6,6 +6,8 @@ package db
 
 import (
 	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrCancelled represents an error due to context cancellation
@@ -45,7 +47,8 @@ func (err ErrSSHDisabled) Error() string {
 
 // ErrNotExist represents a non-exist error.
 type ErrNotExist struct {
-	ID int64
+	Resource string
+	ID       int64
 }
 
 // IsErrNotExist checks if an error is an ErrNotExist
@@ -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
 }
diff --git a/models/db/index.go b/models/db/index.go
index 9b164db1fa6d2..f64bf6bfb5782 100644
--- a/models/db/index.go
+++ b/models/db/index.go
@@ -8,45 +8,18 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strconv"
 
 	"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
-}
-
 var (
 	// ErrResouceOutdated represents an error when request resource outdated
 	ErrResouceOutdated = errors.New("resource outdated")
@@ -54,58 +27,102 @@ var (
 	ErrGetResourceIndexFailed = errors.New("get resource index failed")
 )
 
-const (
-	// MaxDupIndexAttempts max retry times to create index
-	MaxDupIndexAttempts = 3
-)
+// SyncMaxResourceIndex sync the max index with the resource
+func SyncMaxResourceIndex(ctx context.Context, tableName string, groupID, maxIndex int64) (err error) {
+	e := GetEngine(ctx)
 
-// 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
-		}
+	// 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 0, err
+			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 idx, nil
 	}
-	return 0, ErrGetResourceIndexFailed
+	return nil
 }
 
-// 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)
-	return err
+func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) {
+	res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+
+		"VALUES (?,1) ON CONFLICT (group_id) DO UPDATE SET max_index = %s.max_index+1 RETURNING max_index",
+		tableName, tableName), groupID)
+	if err != nil {
+		return 0, err
+	}
+	if len(res) == 0 {
+		return 0, ErrGetResourceIndexFailed
+	}
+	return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
 }
 
-// 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) {
+	if setting.Database.UsePostgreSQL {
+		return postgresGetNextResourceIndex(ctx, tableName, groupID)
+	}
+
+	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 {
-		return 0, err
+	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
-	}
-	if err := commiter.Commit(); err != nil {
-		return 0, err
+		return 0, errors.New("impossible error when GetNextResourceIndex, upsert succeeded but no record can be selected")
 	}
-	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
 }
diff --git a/models/db/index_test.go b/models/db/index_test.go
new file mode 100644
index 0000000000000..1ea30e2b60c34
--- /dev/null
+++ b/models/db/index_test.go
@@ -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
+}
diff --git a/models/db/iterate.go b/models/db/iterate.go
new file mode 100644
index 0000000000000..3d4fa06eeb96e
--- /dev/null
+++ b/models/db/iterate.go
@@ -0,0 +1,34 @@
+// 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
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/modules/setting"
+)
+
+// IterateObjects iterate all the Bean object
+func IterateObjects[Object any](ctx context.Context, f func(repo *Object) error) error {
+	var start int
+	batchSize := setting.Database.IterateBufferSize
+	sess := GetEngine(ctx)
+	for {
+		repos := make([]*Object, 0, batchSize)
+		if err := sess.Limit(batchSize, start).Find(&repos); err != nil {
+			return err
+		}
+		if len(repos) == 0 {
+			return nil
+		}
+		start += len(repos)
+
+		for _, repo := range repos {
+			if err := f(repo); err != nil {
+				return err
+			}
+		}
+	}
+}
diff --git a/models/db/list_options.go b/models/db/list_options.go
index d1d52b6667e4c..54f6d945c81f6 100644
--- a/models/db/list_options.go
+++ b/models/db/list_options.go
@@ -58,7 +58,7 @@ func (opts *ListOptions) GetSkipTake() (skip, take int) {
 func (opts *ListOptions) GetStartEnd() (start, end int) {
 	start, take := opts.GetSkipTake()
 	end = start + take
-	return
+	return start, end
 }
 
 // SetDefaultValues sets default values
diff --git a/models/db/log.go b/models/db/log.go
index f9febf440e2b2..4c497fdfd72c3 100644
--- a/models/db/log.go
+++ b/models/db/log.go
@@ -6,6 +6,7 @@ package db
 
 import (
 	"fmt"
+	"sync/atomic"
 
 	"code.gitea.io/gitea/modules/log"
 
@@ -14,15 +15,19 @@ import (
 
 // XORMLogBridge a logger bridge from Logger to xorm
 type XORMLogBridge struct {
-	showSQL bool
-	logger  log.Logger
+	showSQLint *int32
+	logger     log.Logger
 }
 
 // NewXORMLogger inits a log bridge for xorm
 func NewXORMLogger(showSQL bool) xormlog.Logger {
+	showSQLint := int32(0)
+	if showSQL {
+		showSQLint = 1
+	}
 	return &XORMLogBridge{
-		showSQL: showSQL,
-		logger:  log.GetLogger("xorm"),
+		showSQLint: &showSQLint,
+		logger:     log.GetLogger("xorm"),
 	}
 }
 
@@ -94,14 +99,16 @@ func (l *XORMLogBridge) SetLevel(lvl xormlog.LogLevel) {
 
 // ShowSQL set if record SQL
 func (l *XORMLogBridge) ShowSQL(show ...bool) {
-	if len(show) > 0 {
-		l.showSQL = show[0]
-	} else {
-		l.showSQL = true
+	showSQL := int32(1)
+	if len(show) > 0 && !show[0] {
+		showSQL = 0
 	}
+	atomic.StoreInt32(l.showSQLint, showSQL)
 }
 
 // IsShowSQL if record SQL
 func (l *XORMLogBridge) IsShowSQL() bool {
-	return l.showSQL
+	showSQL := atomic.LoadInt32(l.showSQLint)
+
+	return showSQL == 1
 }
diff --git a/models/db/name.go b/models/db/name.go
index 9c9d18f184748..a05d1a789be1a 100644
--- a/models/db/name.go
+++ b/models/db/name.go
@@ -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
diff --git a/models/db/sql_postgres_with_schema.go b/models/db/sql_postgres_with_schema.go
index d6b6262927c4a..4bbd12bdebc5d 100644
--- a/models/db/sql_postgres_with_schema.go
+++ b/models/db/sql_postgres_with_schema.go
@@ -44,7 +44,7 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
 		_, err := execer.Exec(`SELECT set_config(
 			'search_path',
 			$1 || ',' || current_setting('search_path'),
-			false)`, []driver.Value{schemaValue}) //nolint
+			false)`, []driver.Value{schemaValue})
 		if err != nil {
 			_ = conn.Close()
 			return nil, err
diff --git a/models/error.go b/models/error.go
index 3c617904f8dd8..f4c4bc8f67cb8 100644
--- a/models/error.go
+++ b/models/error.go
@@ -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.
@@ -57,101 +58,14 @@ func (err ErrUserOwnPackages) Error() string {
 	return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID)
 }
 
-//  __      __.__ __   .__
-// /  \    /  \__|  | _|__|
-// \   \/\/   /  |  |/ /  |
-//  \        /|  |    <|  |
-//   \__/\  / |__|__|_ \__|
-//        \/          \/
-
-// ErrWikiAlreadyExist represents a "WikiAlreadyExist" kind of error.
-type ErrWikiAlreadyExist struct {
-	Title string
-}
-
-// IsErrWikiAlreadyExist checks if an error is an ErrWikiAlreadyExist.
-func IsErrWikiAlreadyExist(err error) bool {
-	_, ok := err.(ErrWikiAlreadyExist)
-	return ok
-}
-
-func (err ErrWikiAlreadyExist) Error() string {
-	return fmt.Sprintf("wiki page already exists [title: %s]", err.Title)
-}
-
-// ErrWikiReservedName represents a reserved name error.
-type ErrWikiReservedName struct {
-	Title string
-}
-
-// IsErrWikiReservedName checks if an error is an ErrWikiReservedName.
-func IsErrWikiReservedName(err error) bool {
-	_, ok := err.(ErrWikiReservedName)
-	return ok
-}
-
-func (err ErrWikiReservedName) Error() string {
-	return fmt.Sprintf("wiki title is reserved: %s", err.Title)
-}
-
-// ErrWikiInvalidFileName represents an invalid wiki file name.
-type ErrWikiInvalidFileName struct {
-	FileName string
-}
-
-// IsErrWikiInvalidFileName checks if an error is an ErrWikiInvalidFileName.
-func IsErrWikiInvalidFileName(err error) bool {
-	_, ok := err.(ErrWikiInvalidFileName)
-	return ok
-}
-
-func (err ErrWikiInvalidFileName) Error() string {
-	return fmt.Sprintf("Invalid wiki filename: %s", err.FileName)
-}
-
-//    _____                                   ___________     __
-//   /  _  \   ____  ____  ____   ______ _____\__    ___/___ |  | __ ____   ____
-//  /  /_\  \_/ ___\/ ___\/ __ \ /  ___//  ___/ |    | /  _ \|  |/ // __ \ /    \
-// /    |    \  \__\  \__\  ___/ \___ \ \___ \  |    |(  <_> )    <\  ___/|   |  \
-// \____|__  /\___  >___  >___  >____  >____  > |____| \____/|__|_ \\___  >___|  /
-//         \/     \/    \/    \/     \/     \/                    \/    \/     \/
-
-// ErrAccessTokenNotExist represents a "AccessTokenNotExist" kind of error.
-type ErrAccessTokenNotExist struct {
-	Token string
-}
-
-// IsErrAccessTokenNotExist checks if an error is a ErrAccessTokenNotExist.
-func IsErrAccessTokenNotExist(err error) bool {
-	_, ok := err.(ErrAccessTokenNotExist)
-	return ok
-}
-
-func (err ErrAccessTokenNotExist) Error() string {
-	return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
-}
-
-// ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
-type ErrAccessTokenEmpty struct{}
-
-// IsErrAccessTokenEmpty checks if an error is a ErrAccessTokenEmpty.
-func IsErrAccessTokenEmpty(err error) bool {
-	_, ok := err.(ErrAccessTokenEmpty)
-	return ok
-}
-
-func (err ErrAccessTokenEmpty) Error() string {
-	return "access token is empty"
-}
-
 // ErrNoPendingRepoTransfer is an error type for repositories without a pending
 // transfer request
 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
@@ -161,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 {
@@ -178,21 +96,8 @@ func (err ErrRepoTransferInProgress) Error() string {
 	return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
-// ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error.
-type ErrForkAlreadyExist struct {
-	Uname    string
-	RepoName string
-	ForkName string
-}
-
-// IsErrForkAlreadyExist checks if an error is an ErrForkAlreadyExist.
-func IsErrForkAlreadyExist(err error) bool {
-	_, ok := err.(ErrForkAlreadyExist)
-	return ok
-}
-
-func (err ErrForkAlreadyExist) Error() string {
-	return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName)
+func (err ErrRepoTransferInProgress) Unwrap() error {
+	return util.ErrAlreadyExist
 }
 
 // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
@@ -228,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
@@ -243,35 +152,8 @@ func (err ErrUpdateTaskNotExist) Error() string {
 	return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
 }
 
-// ErrReleaseAlreadyExist represents a "ReleaseAlreadyExist" kind of error.
-type ErrReleaseAlreadyExist struct {
-	TagName string
-}
-
-// IsErrReleaseAlreadyExist checks if an error is a ErrReleaseAlreadyExist.
-func IsErrReleaseAlreadyExist(err error) bool {
-	_, ok := err.(ErrReleaseAlreadyExist)
-	return ok
-}
-
-func (err ErrReleaseAlreadyExist) Error() string {
-	return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName)
-}
-
-// ErrReleaseNotExist represents a "ReleaseNotExist" kind of error.
-type ErrReleaseNotExist struct {
-	ID      int64
-	TagName string
-}
-
-// IsErrReleaseNotExist checks if an error is a ErrReleaseNotExist.
-func IsErrReleaseNotExist(err error) bool {
-	_, ok := err.(ErrReleaseNotExist)
-	return ok
-}
-
-func (err ErrReleaseNotExist) Error() string {
-	return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
+func (err ErrUpdateTaskNotExist) Unwrap() error {
+	return util.ErrNotExist
 }
 
 // ErrInvalidTagName represents a "InvalidTagName" kind of error.
@@ -289,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
@@ -304,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
@@ -319,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
@@ -335,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
@@ -350,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
@@ -365,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
@@ -386,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
@@ -405,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
+}
+
 // __________                             .__
 // \______   \____________    ____   ____ |  |__
 //  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
@@ -427,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
@@ -442,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
@@ -457,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
@@ -473,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
@@ -488,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
@@ -503,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
@@ -535,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
@@ -581,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
@@ -657,71 +603,3 @@ func (err ErrPullRequestHasMerged) Error() string {
 	return fmt.Sprintf("pull request has merged [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
 		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
 }
-
-//  _________ __                                __         .__
-//  /   _____//  |_  ____ ________  _  _______ _/  |_  ____ |  |__
-//  \_____  \\   __\/  _ \\____ \ \/ \/ /\__  \\   __\/ ___\|  |  \
-//  /        \|  | (  <_> )  |_> >     /  / __ \|  | \  \___|   Y  \
-//  /_______  /|__|  \____/|   __/ \/\_/  (____  /__|  \___  >___|  /
-// \/             |__|                \/          \/     \/
-
-// ErrStopwatchNotExist represents a "Stopwatch Not Exist" kind of error.
-type ErrStopwatchNotExist struct {
-	ID int64
-}
-
-// IsErrStopwatchNotExist checks if an error is a ErrStopwatchNotExist.
-func IsErrStopwatchNotExist(err error) bool {
-	_, ok := err.(ErrStopwatchNotExist)
-	return ok
-}
-
-func (err ErrStopwatchNotExist) Error() string {
-	return fmt.Sprintf("stopwatch does not exist [id: %d]", err.ID)
-}
-
-// ___________                     __              .______________.__
-// \__    ___/___________    ____ |  | __ ____   __| _/\__    ___/|__| _____   ____
-// |    |  \_  __ \__  \ _/ ___\|  |/ // __ \ / __ |   |    |   |  |/     \_/ __ \
-// |    |   |  | \// __ \\  \___|    <\  ___// /_/ |   |    |   |  |  Y Y  \  ___/
-// |____|   |__|  (____  /\___  >__|_ \\___  >____ |   |____|   |__|__|_|  /\___  >
-// \/     \/     \/    \/     \/                     \/     \/
-
-// ErrTrackedTimeNotExist represents a "TrackedTime Not Exist" kind of error.
-type ErrTrackedTimeNotExist struct {
-	ID int64
-}
-
-// IsErrTrackedTimeNotExist checks if an error is a ErrTrackedTimeNotExist.
-func IsErrTrackedTimeNotExist(err error) bool {
-	_, ok := err.(ErrTrackedTimeNotExist)
-	return ok
-}
-
-func (err ErrTrackedTimeNotExist) Error() string {
-	return fmt.Sprintf("tracked time does not exist [id: %d]", err.ID)
-}
-
-//  ____ ___        .__                    .___
-// |    |   \______ |  |   _________     __| _/
-// |    |   /\____ \|  |  /  _ \__  \   / __ |
-// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |
-// |______/  |   __/|____/\____(____  /\____ |
-//           |__|                   \/      \/
-//
-
-// ErrUploadNotExist represents a "UploadNotExist" kind of error.
-type ErrUploadNotExist struct {
-	ID   int64
-	UUID string
-}
-
-// IsErrUploadNotExist checks if an error is a ErrUploadNotExist.
-func IsErrUploadNotExist(err error) bool {
-	_, ok := err.(ErrUploadNotExist)
-	return ok
-}
-
-func (err ErrUploadNotExist) Error() string {
-	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
-}
diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml
index 4027e5fe92268..446502843ef48 100644
--- a/models/fixtures/access.yml
+++ b/models/fixtures/access.yml
@@ -124,3 +124,15 @@
   repo_id: 24
   mode: 1
 
+-
+  id: 22
+  user_id: 31
+  repo_id: 27
+  mode: 4
+
+-
+  id: 23
+  user_id: 31
+  repo_id: 28
+  mode: 4
+
diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml
index 8612f6ece7088..9ad43fa2b7eb6 100644
--- a/models/fixtures/attachment.yml
+++ b/models/fixtures/attachment.yml
@@ -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
diff --git a/models/fixtures/follow.yml b/models/fixtures/follow.yml
index 480fa065c76d1..b8d35828bf170 100644
--- a/models/fixtures/follow.yml
+++ b/models/fixtures/follow.yml
@@ -12,3 +12,8 @@
   id: 3
   user_id: 2
   follow_id: 8
+
+-
+  id: 4
+  user_id: 31
+  follow_id: 33
diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml
index bb662345cdff6..6dbb10151abf8 100644
--- a/models/fixtures/hook_task.yml
+++ b/models/fixtures/hook_task.yml
@@ -1,6 +1,5 @@
 -
   id: 1
-  repo_id: 1
   hook_id: 1
   uuid: uuid1
   is_delivered: true
diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index 39dacc92ffd98..4dea8add138ed 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -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
diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml
index 1b7ce746816ee..57bf80445712f 100644
--- a/models/fixtures/label.yml
+++ b/models/fixtures/label.yml
@@ -15,10 +15,11 @@
   color: '#000000'
   num_issues: 1
   num_closed_issues: 1
+
 -
   id: 3
   repo_id: 0
-  org_id:  3
+  org_id: 3
   name: orglabel3
   color: '#abcdef'
   num_issues: 0
@@ -32,7 +33,7 @@
   color: '#000000'
   num_issues: 1
   num_closed_issues: 0
-  
+
 -
   id: 5
   repo_id: 10
diff --git a/models/fixtures/milestone.yml b/models/fixtures/milestone.yml
index 4dd3445940a50..87c30cc96c4ac 100644
--- a/models/fixtures/milestone.yml
+++ b/models/fixtures/milestone.yml
@@ -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,14 +39,16 @@
   is_closed: false
   num_issues: 0
   num_closed_issues: 0
+  completeness: 0
   deadline_unix: 253370764800
 
-- 
+-
   id: 5
   repo_id: 10
-  name: milestone of repo 10 
+  name: milestone of repo 10
   content: for testing with PRs
   is_closed: false
   num_issues: 0
   num_closed_issues: 0
+  completeness: 0
   deadline_unix: 253370764800
diff --git a/models/fixtures/oauth2_application.yml b/models/fixtures/oauth2_application.yml
index a13e20b10e2a3..2f38cb58b6169 100644
--- a/models/fixtures/oauth2_application.yml
+++ b/models/fixtures/oauth2_application.yml
@@ -4,6 +4,17 @@
   name: "Test"
   client_id: "da7da3ba-9a13-4167-856f-3899de0b0138"
   client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
-  redirect_uris: '["a"]'
+  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
diff --git a/models/fixtures/oauth2_authorization_code.yml b/models/fixtures/oauth2_authorization_code.yml
index 2abce16354a1b..d29502164e67f 100644
--- a/models/fixtures/oauth2_authorization_code.yml
+++ b/models/fixtures/oauth2_authorization_code.yml
@@ -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
diff --git a/models/fixtures/oauth2_grant.yml b/models/fixtures/oauth2_grant.yml
index e52a2bce959e6..e63286878b20f 100644
--- a/models/fixtures/oauth2_grant.yml
+++ b/models/fixtures/oauth2_grant.yml
@@ -20,4 +20,12 @@
   counter: 1
   scope: "openid profile email"
   created_unix: 1546869730
-  updated_unix: 1546869730
\ No newline at end of file
+  updated_unix: 1546869730
+
+- id: 4
+  user_id: 99
+  application_id: 2
+  counter: 1
+  scope: "whatever"
+  created_unix: 1546869730
+  updated_unix: 1546869730
diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml
index a0bc4b9b43a83..d6bbdaa9da268 100644
--- a/models/fixtures/org_user.yml
+++ b/models/fixtures/org_user.yml
@@ -63,3 +63,15 @@
   uid: 29
   org_id: 17
   is_public: true
+
+-
+  id: 12
+  uid: 2
+  org_id: 17
+  is_public: true
+
+-
+  id: 13
+  uid: 31
+  org_id: 19
+  is_public: true
diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml
index d45baa711c6e7..165437f0328dd 100644
--- a/models/fixtures/pull_request.yml
+++ b/models/fixtures/pull_request.yml
@@ -60,8 +60,8 @@
   head_repo_id: 1
   base_repo_id: 1
   head_branch: pr-to-update
-  base_branch: branch1
-  merge_base: 1234567890abcdef
+  base_branch: branch2
+  merge_base: 985f0301dba5e7b34be866819cd15ad3d8f508ee
   has_merged: false
 
 -
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 82b3ed16dcccf..f09953be7e5ad 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -4,19 +4,29 @@
   owner_name: user2
   lower_name: repo1
   name: repo1
-  is_archived: false
-  is_empty: false
-  is_private: false
+  num_watches: 4
+  num_stars: 0
+  num_forks: 0
   num_issues: 2
   num_closed_issues: 1
   num_pulls: 3
   num_closed_pulls: 0
   num_milestones: 3
   num_closed_milestones: 1
-  num_watches: 4
   num_projects: 1
   num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 2
@@ -24,16 +34,29 @@
   owner_name: user2
   lower_name: repo2
   name: repo2
-  is_empty: false
-  is_archived: false
-  is_private: true
+  num_watches: 0
+  num_stars: 1
+  num_forks: 0
   num_issues: 2
   num_closed_issues: 1
   num_pulls: 0
   num_closed_pulls: 0
-  num_stars: 1
-  close_issues_via_commit_in_any_branch: true
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: true
 
 -
   id: 3
@@ -41,16 +64,29 @@
   owner_name: user3
   lower_name: repo3
   name: repo3
-  is_empty: false
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 1
   num_closed_issues: 0
   num_pulls: 1
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
   num_projects: 1
   num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 4
@@ -58,16 +94,29 @@
   owner_name: user5
   lower_name: repo4
   name: repo4
-  is_empty: false
-  is_private: false
+  num_watches: 0
+  num_stars: 1
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_stars: 1
+  num_milestones: 0
+  num_closed_milestones: 0
   num_projects: 0
   num_closed_projects: 1
+  is_private: false
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 5
@@ -75,14 +124,29 @@
   owner_name: user3
   lower_name: repo5
   name: repo5
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 1
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: true
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 6
@@ -90,13 +154,29 @@
   owner_name: user10
   lower_name: repo6
   name: repo6
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 7
@@ -104,13 +184,29 @@
   owner_name: user10
   lower_name: repo7
   name: repo7
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 8
@@ -118,13 +214,29 @@
   owner_name: user10
   lower_name: repo8
   name: repo8
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 9
@@ -132,13 +244,29 @@
   owner_name: user11
   lower_name: repo9
   name: repo9
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 10
@@ -146,32 +274,59 @@
   owner_name: user12
   lower_name: repo10
   name: repo10
-  is_empty: false
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 1
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 1
   num_closed_pulls: 0
   num_milestones: 1
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
-  num_forks: 1
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 11
-  fork_id: 10
   owner_id: 13
   owner_name: user13
   lower_name: repo11
   name: repo11
-  is_empty: false
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 10
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 12
@@ -179,13 +334,29 @@
   owner_name: user14
   lower_name: test_repo_12
   name: test_repo_12
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 13
@@ -193,13 +364,29 @@
   owner_name: user14
   lower_name: test_repo_13
   name: test_repo_13
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 14
@@ -208,13 +395,29 @@
   lower_name: test_repo_14
   name: test_repo_14
   description: test_description_14
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 15
@@ -222,9 +425,29 @@
   owner_name: user2
   lower_name: repo15
   name: repo15
-  is_empty: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
   is_private: true
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 16
@@ -232,14 +455,29 @@
   owner_name: user2
   lower_name: repo16
   name: repo16
-  is_empty: false
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 17
@@ -247,15 +485,29 @@
   owner_name: user15
   lower_name: big_test_public_1
   name: big_test_public_1
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 18
@@ -263,14 +515,29 @@
   owner_name: user15
   lower_name: big_test_public_2
   name: big_test_public_2
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 19
@@ -278,14 +545,29 @@
   owner_name: user15
   lower_name: big_test_private_1
   name: big_test_private_1
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 20
@@ -293,14 +575,29 @@
   owner_name: user15
   lower_name: big_test_private_2
   name: big_test_private_2
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 21
@@ -308,14 +605,29 @@
   owner_name: user16
   lower_name: big_test_public_3
   name: big_test_public_3
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 22
@@ -323,14 +635,29 @@
   owner_name: user16
   lower_name: big_test_private_3
   name: big_test_private_3
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 23
@@ -338,14 +665,29 @@
   owner_name: user17
   lower_name: big_test_public_4
   name: big_test_public_4
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 24
@@ -353,14 +695,29 @@
   owner_name: user17
   lower_name: big_test_private_4
   name: big_test_private_4
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 25
@@ -368,15 +725,29 @@
   owner_name: user20
   lower_name: big_test_public_mirror_5
   name: big_test_public_mirror_5
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: true
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 26
@@ -384,15 +755,29 @@
   owner_name: user20
   lower_name: big_test_private_mirror_5
   name: big_test_private_mirror_5
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: true
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 27
@@ -400,16 +785,29 @@
   owner_name: user19
   lower_name: big_test_public_mirror_6
   name: big_test_public_mirror_6
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 1
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: true
-  num_forks: 1
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 28
@@ -417,48 +815,89 @@
   owner_name: user19
   lower_name: big_test_private_mirror_6
   name: big_test_private_mirror_6
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 1
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
-  num_watches: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: true
-  num_forks: 1
-  is_fork: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 29
-  fork_id: 27
   owner_id: 20
   owner_name: user20
   lower_name: big_test_public_fork_7
   name: big_test_public_fork_7
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: true
   status: 0
+  is_fork: true
+  fork_id: 27
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 30
-  fork_id: 28
   owner_id: 20
   owner_name: user20
   lower_name: big_test_private_fork_7
   name: big_test_private_fork_7
-  is_private: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 0
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
-  is_fork: true
   status: 0
+  is_fork: true
+  fork_id: 28
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 31
@@ -466,13 +905,29 @@
   owner_name: user2
   lower_name: repo20
   name: repo20
-  is_empty: false
-  is_private: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 32 # org public repo
@@ -480,12 +935,29 @@
   owner_name: user3
   lower_name: repo21
   name: repo21
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 2
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 33
@@ -493,9 +965,29 @@
   owner_name: user2
   lower_name: utf8
   name: utf8
-  is_empty: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
   is_private: false
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 34
@@ -503,12 +995,29 @@
   owner_name: user21
   lower_name: golang
   name: golang
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 35
@@ -516,12 +1025,29 @@
   owner_name: user21
   lower_name: graphql
   name: graphql
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 36
@@ -529,13 +1055,29 @@
   owner_name: user2
   lower_name: commits_search_test
   name: commits_search_test
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 37
@@ -543,13 +1085,29 @@
   owner_name: user2
   lower_name: git_hooks_test
   name: git_hooks_test
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 38
@@ -557,13 +1115,29 @@
   owner_name: limited_org
   lower_name: public_repo_on_limited_org
   name: public_repo_on_limited_org
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 39
@@ -571,13 +1145,29 @@
   owner_name: limited_org
   lower_name: private_repo_on_limited_org
   name: private_repo_on_limited_org
-  is_empty: false
-  is_private: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 40
@@ -585,13 +1175,29 @@
   owner_name: privated_org
   lower_name: public_repo_on_private_org
   name: public_repo_on_private_org
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 41
@@ -599,12 +1205,29 @@
   owner_name: privated_org
   lower_name: private_repo_on_private_org
   name: private_repo_on_private_org
-  is_empty: false
-  is_private: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: false
+  is_archived: false
   is_mirror: false
+  status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 42
@@ -612,14 +1235,29 @@
   owner_name: user2
   lower_name: glob
   name: glob
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 1
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
   num_milestones: 1
-  is_mirror:
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
   is_archived: false
+  is_mirror: false
+  status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 43
@@ -627,12 +1265,29 @@
   owner_name: org26
   lower_name: repo26
   name: repo26
-  is_private: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: true
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 44
@@ -640,14 +1295,29 @@
   owner_name: user27
   lower_name: template1
   name: template1
-  is_empty: false
-  is_private: false
-  is_template: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: true
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 45
@@ -655,13 +1325,29 @@
   owner_name: user27
   lower_name: template2
   name: template2
-  is_private: false
-  is_template: true
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: true
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: true
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 46
@@ -669,13 +1355,29 @@
   owner_name: org26
   lower_name: repo_external_tracker
   name: repo_external_tracker
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 47
@@ -683,13 +1385,29 @@
   owner_name: org26
   lower_name: repo_external_tracker_numeric
   name: repo_external_tracker_numeric
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 48
@@ -697,14 +1415,29 @@
   owner_name: org26
   lower_name: repo_external_tracker_alpha
   name: repo_external_tracker_alpha
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
   num_pulls: 1
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 49
@@ -712,13 +1445,29 @@
   owner_name: user27
   lower_name: repo49
   name: repo49
-  is_empty: false
-  is_private: false
+  num_watches: 0
   num_stars: 0
   num_forks: 0
   num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
   is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 50
@@ -726,19 +1475,29 @@
   owner_name: user30
   lower_name: repo50
   name: repo50
-  is_archived: false
-  is_empty: false
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 1
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
   num_milestones: 0
   num_closed_milestones: 0
-  num_watches: 0
   num_projects: 0
   num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 51
@@ -746,19 +1505,29 @@
   owner_name: user30
   lower_name: repo51
   name: repo51
-  is_archived: true
-  is_empty: false
-  is_private: false
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
   num_issues: 1
   num_closed_issues: 0
   num_pulls: 0
   num_closed_pulls: 0
   num_milestones: 0
   num_closed_milestones: 0
-  num_watches: 0
   num_projects: 0
   num_closed_projects: 0
+  is_private: false
+  is_empty: false
+  is_archived: true
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
 
 -
   id: 52
@@ -766,6 +1535,26 @@
   owner_name: user30
   lower_name: empty
   name: empty
-  is_empty: true
+  num_watches: 0
+  num_stars: 0
+  num_forks: 0
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_milestones: 0
+  num_closed_milestones: 0
+  num_projects: 0
+  num_closed_projects: 0
   is_private: true
+  is_empty: true
+  is_archived: false
+  is_mirror: false
   status: 0
+  is_fork: false
+  fork_id: 0
+  is_template: false
+  template_id: 0
+  size: 0
+  is_fsck_enabled: true
+  close_issues_via_commit_in_any_branch: false
diff --git a/models/fixtures/system_setting.yml b/models/fixtures/system_setting.yml
new file mode 100644
index 0000000000000..6c960168fcb81
--- /dev/null
+++ b/models/fixtures/system_setting.yml
@@ -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
diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml
index f6dfd1e9d0b96..ea47a33f1c4db 100644
--- a/models/fixtures/team.yml
+++ b/models/fixtures/team.yml
@@ -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
 
 -
@@ -55,7 +60,8 @@
   name: Owners
   authorize: 4 # owner
   num_repos: 2
-  num_members: 1
+  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
diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml
index 8f21164df472a..845741effddf8 100644
--- a/models/fixtures/team_user.yml
+++ b/models/fixtures/team_user.yml
@@ -87,3 +87,9 @@
   org_id: 17
   team_id: 9
   uid: 29
+
+-
+  id: 16
+  org_id: 19
+  team_id: 6
+  uid: 31
diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml
index 6cd0b37fa1721..055addf510e20 100644
--- a/models/fixtures/topic.yml
+++ b/models/fixtures/topic.yml
@@ -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
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 67ba869c76b08..0e3348e146a82 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -4,589 +4,1219 @@
   id: 1
   lower_name: user1
   name: user1
-  login_name: user1
   full_name: User One
   email: user1@example.com
+  keep_email_private: false
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user1
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: true
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar1
   avatar_email: user1@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 2
   lower_name: user2
   name: user2
-  login_name: user2
-  full_name: "   < Ur Tw ><  "
+  full_name: '   < Ur Tw ><  '
   email: user2@example.com
   keep_email_private: true
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user2
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar2
   avatar_email: user2@example.com
-  num_repos: 9
-  num_stars: 2
+  use_custom_avatar: false
   num_followers: 2
   num_following: 1
-  is_active: true
+  num_stars: 2
+  num_repos: 9
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 3
   lower_name: user3
   name: user3
-  login_name: user3
-  full_name: " <<<< >> >> > >> > >>> >> "
+  full_name: ' <<<< >> >> > >> > >>> >> '
   email: user3@example.com
+  keep_email_private: false
   email_notifications_preference: onmention
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: user3
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar3
   avatar_email: user3@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 3
-  num_members: 3
   num_teams: 4
+  num_members: 3
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 4
   lower_name: user4
   name: user4
-  login_name: user4
-  full_name: "          "
+  full_name: '          '
   email: user4@example.com
+  keep_email_private: false
   email_notifications_preference: onmention
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user4
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar4
   avatar_email: user4@example.com
-  num_repos: 0
+  use_custom_avatar: false
+  num_followers: 0
   num_following: 1
-  is_active: true
+  num_stars: 0
+  num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 5
   lower_name: user5
   name: user5
-  login_name: user5
   full_name: User Five
   email: user5@example.com
+  keep_email_private: false
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user5
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: false
+  prohibit_login: false
   avatar: avatar5
   avatar_email: user5@example.com
-  num_repos: 1
-  allow_create_organization: false
-  is_active: true
+  use_custom_avatar: false
+  num_followers: 0
   num_following: 0
+  num_stars: 0
+  num_repos: 1
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 6
   lower_name: user6
   name: user6
-  login_name: user6
   full_name: User Six
   email: user6@example.com
+  keep_email_private: false
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: user6
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar6
   avatar_email: user6@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  num_members: 2
   num_teams: 2
+  num_members: 2
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 7
   lower_name: user7
   name: user7
-  login_name: user7
   full_name: User Seven
   email: user7@example.com
+  keep_email_private: false
   email_notifications_preference: disabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: user7
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar7
   avatar_email: user7@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  num_members: 1
   num_teams: 1
+  num_members: 1
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 8
   lower_name: user8
   name: user8
-  login_name: user8
   full_name: User Eight
   email: user8@example.com
+  keep_email_private: false
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user8
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar8
   avatar_email: user8@example.com
-  num_repos: 0
-  is_active: true
+  use_custom_avatar: false
   num_followers: 1
   num_following: 1
+  num_stars: 0
+  num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 9
   lower_name: user9
   name: user9
-  login_name: user9
   full_name: User Nine
   email: user9@example.com
+  keep_email_private: false
   email_notifications_preference: onmention
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user9
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar9
   avatar_email: user9@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  is_active: false
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 10
   lower_name: user10
   name: user10
-  login_name: user10
   full_name: User Ten
   email: user10@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user10
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar10
   avatar_email: user10@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 3
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 11
   lower_name: user11
   name: user11
-  login_name: user11
   full_name: User Eleven
   email: user11@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user11
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar11
   avatar_email: user11@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 1
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 12
   lower_name: user12
   name: user12
-  login_name: user12
   full_name: User 12
   email: user12@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user12
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar12
   avatar_email: user12@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 1
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 13
   lower_name: user13
   name: user13
-  login_name: user13
   full_name: User 13
   email: user13@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user13
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar13
   avatar_email: user13@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 1
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 14
   lower_name: user14
   name: user14
-  login_name: user14
   full_name: User 14
   email: user14@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user14
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar14
   avatar_email: user13@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 3
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 15
   lower_name: user15
   name: user15
-  login_name: user15
   full_name: User 15
   email: user15@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user15
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar15
   avatar_email: user15@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 4
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 16
   lower_name: user16
   name: user16
-  login_name: user16
   full_name: User 16
   email: user16@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user16
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar16
   avatar_email: user16@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 17
   lower_name: user17
   name: user17
-  login_name: user17
   full_name: User 17
   email: user17@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: user17
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar17
   avatar_email: user17@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
-  num_members: 3
   num_teams: 3
+  num_members: 4
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 18
   lower_name: user18
   name: user18
-  login_name: user18
   full_name: User 18
   email: user18@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user18
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar18
   avatar_email: user18@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 19
   lower_name: user19
   name: user19
-  login_name: user19
   full_name: User 19
   email: user19@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: user19
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar19
   avatar_email: user19@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
-  num_members: 1
   num_teams: 1
+  num_members: 2
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 20
   lower_name: user20
   name: user20
-  login_name: user20
   full_name: User 20
   email: user20@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user20
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar20
   avatar_email: user20@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 4
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 21
   lower_name: user21
   name: user21
-  login_name: user21
   full_name: User 21
   email: user21@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user21
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar21
   avatar_email: user21@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 22
   lower_name: limited_org
   name: limited_org
-  login_name: limited_org
   full_name: Limited Org
   email: limited_org@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: limited_org
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar22
   avatar_email: limited_org@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
-  num_members: 0
   num_teams: 0
+  num_members: 0
   visibility: 1
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 23
   lower_name: privated_org
   name: privated_org
-  login_name: privated_org
   full_name: Privated Org
   email: privated_org@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: privated_org
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar23
   avatar_email: privated_org@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 2
-  is_active: true
-  num_members: 0
   num_teams: 0
+  num_members: 0
   visibility: 2
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 24
   lower_name: user24
   name: user24
-  login_name: user24
-  full_name: "user24"
+  full_name: user24
   email: user24@example.com
   keep_email_private: true
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user24
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar24
   avatar_email: user24@example.com
-  num_repos: 0
-  num_stars: 0
+  use_custom_avatar: false
   num_followers: 0
   num_following: 0
-  is_active: true
+  num_stars: 0
+  num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 25
   lower_name: org25
   name: org25
-  login_name: org25
-  full_name: "org25"
+  full_name: org25
   email: org25@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: org25
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar25
   avatar_email: org25@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  num_members: 1
   num_teams: 1
+  num_members: 1
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 26
   lower_name: org26
   name: org26
-  login_name: org26
-  full_name: "Org26"
+  full_name: Org26
   email: org26@example.com
+  keep_email_private: false
   email_notifications_preference: onmention
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 1 # organization
+  must_change_password: false
+  login_source: 0
+  login_name: org26
+  type: 1
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: false
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar26
   avatar_email: org26@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 4
-  num_members: 0
   num_teams: 1
+  num_members: 0
+  visibility: 0
   repo_admin_change_team_access: true
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 27
   lower_name: user27
   name: user27
-  login_name: user27
   full_name: User Twenty-Seven
   email: user27@example.com
+  keep_email_private: false
   email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user27
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar27
   avatar_email: user27@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 3
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 28
   lower_name: user28
   name: user28
-  login_name: user28
-  full_name: "user27"
+  full_name: user27
   email: user28@example.com
   keep_email_private: true
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user28
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar28
   avatar_email: user28@example.com
-  num_repos: 0
-  num_stars: 0
+  use_custom_avatar: false
   num_followers: 0
   num_following: 0
-  is_active: true
+  num_stars: 0
+  num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 29
   lower_name: user29
   name: user29
-  login_name: user29
   full_name: User 29
   email: user29@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user29
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
   is_restricted: true
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar29
   avatar_email: user29@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 30
   lower_name: user30
   name: user30
-  login_name: user30
   full_name: User Thirty
   email: user30@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user30
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
   is_restricted: true
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: true
   avatar: avatar29
   avatar_email: user30@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 3
-  is_active: true
-  prohibit_login: true
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 31
   lower_name: user31
   name: user31
-  login_name: user31
-  full_name: "user31"
+  full_name: user31
   email: user31@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
   passwd_hash_algo: argon2
-  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password
-  type: 0 # individual
+  must_change_password: false
+  login_source: 0
+  login_name: user31
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
-  visibility: 2
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar31
   avatar_email: user31@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 1
+  num_stars: 0
   num_repos: 0
-  is_active: true
+  num_teams: 0
+  num_members: 0
+  visibility: 2
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
 
 -
   id: 32
   lower_name: user32
   name: user32
-  login_name: user32
   full_name: User 32 (U2F test)
   email: user32@example.com
-  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
-  type: 0 # individual
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a
+  passwd_hash_algo: argon2
+  must_change_password: false
+  login_source: 0
+  login_name: user32
+  type: 0
   salt: ZogKvWdyEx
+  max_repo_creation: -1
+  is_active: true
   is_admin: false
   is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
   avatar: avatar32
   avatar_email: user30@example.com
+  use_custom_avatar: false
+  num_followers: 0
+  num_following: 0
+  num_stars: 0
   num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 0
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
+
+-
+  id: 33
+  lower_name: user33
+  name: user33
+  full_name: User 33 (Limited Visibility)
+  email: user33@example.com
+  keep_email_private: false
+  email_notifications_preference: enabled
+  passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
+  passwd_hash_algo: argon2
+  must_change_password: false
+  login_source: 0
+  login_name: user33
+  type: 0
+  salt: ZogKvWdyEx
+  max_repo_creation: -1
   is_active: true
+  is_admin: false
+  is_restricted: false
+  allow_git_hook: false
+  allow_import_local: false
+  allow_create_organization: true
+  prohibit_login: false
+  avatar: avatar33
+  avatar_email: user33@example.com
+  use_custom_avatar: false
+  num_followers: 1
+  num_following: 0
+  num_stars: 0
+  num_repos: 0
+  num_teams: 0
+  num_members: 0
+  visibility: 1
+  repo_admin_change_team_access: false
+  theme: ""
+  keep_activity_private: false
diff --git a/models/fixtures/webauthn_credential.yml b/models/fixtures/webauthn_credential.yml
index b4109a03f2b2c..bc43127fcd46d 100644
--- a/models/fixtures/webauthn_credential.yml
+++ b/models/fixtures/webauthn_credential.yml
@@ -1,5 +1,6 @@
-- id: 1
-  name: "WebAuthn credential"
+-
+  id: 1
+  name: WebAuthn credential
   user_id: 32
   attestation_type: none
   sign_count: 0
diff --git a/models/foreignreference/error.go b/models/foreignreference/error.go
index d783a087301ea..a1db773cd29cb 100644
--- a/models/foreignreference/error.go
+++ b/models/foreignreference/error.go
@@ -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
+}
diff --git a/models/git/branches.go b/models/git/branches.go
index 0a44c0a68da68..b17d762dbe5de 100644
--- a/models/git/branches.go
+++ b/models/git/branches.go
@@ -270,7 +270,7 @@ type WhitelistOptions struct {
 // to avoid unnecessary whitelist delete and regenerate.
 func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, protectBranch *ProtectedBranch, opts WhitelistOptions) (err error) {
 	if err = repo.GetOwner(ctx); err != nil {
-		return fmt.Errorf("GetOwner: %v", err)
+		return fmt.Errorf("GetOwner: %w", err)
 	}
 
 	whitelist, err := updateUserWhitelist(ctx, repo, protectBranch.WhitelistUserIDs, opts.UserIDs)
@@ -313,13 +313,13 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
 	// Make sure protectBranch.ID is not 0 for whitelists
 	if protectBranch.ID == 0 {
 		if _, err = db.GetEngine(ctx).Insert(protectBranch); err != nil {
-			return fmt.Errorf("Insert: %v", err)
+			return fmt.Errorf("Insert: %w", err)
 		}
 		return nil
 	}
 
 	if _, err = db.GetEngine(ctx).ID(protectBranch.ID).AllCols().Update(protectBranch); err != nil {
-		return fmt.Errorf("Update: %v", err)
+		return fmt.Errorf("Update: %w", err)
 	}
 
 	return nil
@@ -363,7 +363,7 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
 		whitelist = append(whitelist, userID)
 	}
 
-	return
+	return whitelist, err
 }
 
 // updateUserWhitelist checks whether the user whitelist changed and returns a whitelist with
@@ -378,11 +378,11 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
 	for _, userID := range newWhitelist {
 		user, err := user_model.GetUserByIDCtx(ctx, userID)
 		if err != nil {
-			return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
+			return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
 		}
 		perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
 		if err != nil {
-			return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
+			return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
 		}
 
 		if !perm.CanWrite(unit.TypeCode) {
@@ -392,7 +392,7 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
 		whitelist = append(whitelist, userID)
 	}
 
-	return
+	return whitelist, err
 }
 
 // updateTeamWhitelist checks whether the team whitelist changed and returns a whitelist with
@@ -405,7 +405,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
 
 	teams, err := organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead)
 	if err != nil {
-		return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
+		return nil, fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %w", repo.OwnerID, repo.ID, err)
 	}
 
 	whitelist = make([]int64, 0, len(teams))
@@ -415,7 +415,7 @@ func updateTeamWhitelist(ctx context.Context, repo *repo_model.Repository, curre
 		}
 	}
 
-	return
+	return whitelist, err
 }
 
 // DeleteProtectedBranch removes ProtectedBranch relation between the user and repository.
@@ -539,7 +539,7 @@ func FindRenamedBranch(repoID int64, from string) (branch *RenamedBranch, exist
 	}
 	exist, err = db.GetEngine(db.DefaultContext).Get(branch)
 
-	return
+	return branch, exist, err
 }
 
 // RenameBranch rename a branch
diff --git a/models/git/branches_test.go b/models/git/branches_test.go
index 8102d28d484b1..58c4ad027be10 100644
--- a/models/git/branches_test.go
+++ b/models/git/branches_test.go
@@ -18,8 +18,8 @@ import (
 
 func TestAddDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 
 	assert.Error(t, git_model.AddDeletedBranch(repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
 	assert.NoError(t, git_model.AddDeletedBranch(repo.ID, "test", "5655464564554545466464656", int64(1)))
@@ -27,7 +27,7 @@ func TestAddDeletedBranch(t *testing.T) {
 
 func TestGetDeletedBranches(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	branches, err := git_model.GetDeletedBranches(repo.ID)
 	assert.NoError(t, err)
@@ -36,7 +36,7 @@ func TestGetDeletedBranches(t *testing.T) {
 
 func TestGetDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 
 	assert.NotNil(t, getDeletedBranch(t, firstBranch))
 }
@@ -44,8 +44,8 @@ func TestGetDeletedBranch(t *testing.T) {
 func TestDeletedBranchLoadUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
-	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}).(*git_model.DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
+	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
 
 	branch := getDeletedBranch(t, firstBranch)
 	assert.Nil(t, branch.DeletedBy)
@@ -62,9 +62,9 @@ func TestDeletedBranchLoadUser(t *testing.T) {
 
 func TestRemoveDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
 
 	err := git_model.RemoveDeletedBranchByID(repo.ID, 1)
 	assert.NoError(t, err)
@@ -73,7 +73,7 @@ func TestRemoveDeletedBranch(t *testing.T) {
 }
 
 func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch {
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	deletedBranch, err := git_model.GetDeletedBranchByID(repo.ID, branch.ID)
 	assert.NoError(t, err)
@@ -99,7 +99,7 @@ func TestFindRenamedBranch(t *testing.T) {
 
 func TestRenameBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	_isDefault := false
 
 	ctx, committer, err := db.TxContext()
@@ -117,16 +117,16 @@ func TestRenameBranch(t *testing.T) {
 	}))
 
 	assert.Equal(t, true, _isDefault)
-	repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.Equal(t, "main", repo1.DefaultBranch)
 
-	pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest) // merged
+	pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) // merged
 	assert.Equal(t, "master", pull.BaseBranch)
 
-	pull = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}).(*issues_model.PullRequest) // open
+	pull = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) // open
 	assert.Equal(t, "main", pull.BaseBranch)
 
-	renamedBranch := unittest.AssertExistsAndLoadBean(t, &git_model.RenamedBranch{ID: 2}).(*git_model.RenamedBranch)
+	renamedBranch := unittest.AssertExistsAndLoadBean(t, &git_model.RenamedBranch{ID: 2})
 	assert.Equal(t, "master", renamedBranch.From)
 	assert.Equal(t, "main", renamedBranch.To)
 	assert.Equal(t, int64(1), renamedBranch.RepoID)
@@ -143,7 +143,7 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 	// Get deletedBranch with ID of 1 on repo with ID 2.
 	// This should return a nil branch as this deleted branch
 	// is actually on repo with ID 1.
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 
 	deletedBranch, err := git_model.GetDeletedBranchByID(repo2.ID, 1)
 
@@ -153,7 +153,7 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 
 	// Now get the deletedBranch with ID of 1 on repo with ID 1.
 	// This should return the deletedBranch.
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	deletedBranch, err = git_model.GetDeletedBranchByID(repo1.ID, 1)
 
diff --git a/models/git/commit_status.go b/models/git/commit_status.go
index 54a7b4319944c..9e7fb5f805f27 100644
--- a/models/git/commit_status.go
+++ b/models/git/commit_status.go
@@ -7,8 +7,10 @@ package git
 import (
 	"context"
 	"crypto/sha1"
+	"errors"
 	"fmt"
 	"net/url"
+	"strconv"
 	"strings"
 	"time"
 
@@ -49,92 +51,80 @@ func init() {
 	db.RegisterModel(new(CommitStatusIndex))
 }
 
-// upsertCommitStatusIndex the function will not return until it acquires the lock or receives an error.
-func upsertCommitStatusIndex(ctx context.Context, repoID int64, sha string) (err error) {
-	// An atomic UPSERT operation (INSERT/UPDATE) is the only operation
-	// that ensures that the key is actually locked.
-	switch {
-	case setting.Database.UseSQLite3 || setting.Database.UsePostgreSQL:
-		_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
-			"VALUES (?,?,1) ON CONFLICT (repo_id,sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1",
-			repoID, sha)
-	case setting.Database.UseMySQL:
-		_, err = db.Exec(ctx, "INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
-			"VALUES (?,?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1",
-			repoID, sha)
-	case setting.Database.UseMSSQL:
-		// https://weblogs.sqlteam.com/dang/2009/01/31/upsert-race-condition-with-merge/
-		_, err = db.Exec(ctx, "MERGE `commit_status_index` WITH (HOLDLOCK) as target "+
-			"USING (SELECT ? AS repo_id, ? AS sha) AS src "+
-			"ON src.repo_id = target.repo_id AND src.sha = target.sha "+
-			"WHEN MATCHED THEN UPDATE SET target.max_index = target.max_index+1 "+
-			"WHEN NOT MATCHED THEN INSERT (repo_id, sha, max_index) "+
-			"VALUES (src.repo_id, src.sha, 1);",
-			repoID, sha)
-	default:
-		return fmt.Errorf("database type not supported")
+func postgresGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
+	res, err := db.GetEngine(ctx).Query("INSERT INTO `commit_status_index` (repo_id, sha, max_index) "+
+		"VALUES (?,?,1) ON CONFLICT (repo_id, sha) DO UPDATE SET max_index = `commit_status_index`.max_index+1 RETURNING max_index",
+		repoID, sha)
+	if err != nil {
+		return 0, err
+	}
+	if len(res) == 0 {
+		return 0, db.ErrGetResourceIndexFailed
 	}
-	return
+	return strconv.ParseInt(string(res[0]["max_index"]), 10, 64)
 }
 
 // GetNextCommitStatusIndex retried 3 times to generate a resource index
-func GetNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
-	for i := 0; i < db.MaxDupIndexAttempts; i++ {
-		idx, err := getNextCommitStatusIndex(repoID, sha)
-		if err == db.ErrResouceOutdated {
-			continue
-		}
-		if err != nil {
-			return 0, err
-		}
-		return idx, nil
+func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) {
+	if setting.Database.UsePostgreSQL {
+		return postgresGetCommitStatusIndex(ctx, repoID, sha)
 	}
-	return 0, db.ErrGetResourceIndexFailed
-}
 
-// getNextCommitStatusIndex return the next index
-func getNextCommitStatusIndex(repoID int64, sha string) (int64, error) {
-	ctx, commiter, err := db.TxContext()
+	e := db.GetEngine(ctx)
+
+	// try to update the max_index to next value, and acquire the write-lock for the record
+	res, err := e.Exec("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?", repoID, sha)
 	if err != nil {
 		return 0, err
 	}
-	defer commiter.Close()
-
-	var preIdx int64
-	_, err = db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", repoID, sha).Get(&preIdx)
+	affected, err := res.RowsAffected()
 	if err != nil {
 		return 0, err
 	}
+	if affected == 0 {
+		// this slow path is only for the first time of creating a resource index
+		_, errIns := e.Exec("INSERT INTO `commit_status_index` (repo_id, sha, max_index) VALUES (?, ?, 0)", repoID, sha)
+		res, err = e.Exec("UPDATE `commit_status_index` SET max_index=max_index+1 WHERE repo_id=? AND sha=?", repoID, sha)
+		if err != nil {
+			return 0, err
+		}
 
-	if err := upsertCommitStatusIndex(ctx, repoID, sha); 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 GetNextCommitStatusIndex, insert and update both succeeded but no record is updated")
+			}
+			return 0, errIns
+		}
 	}
 
-	var curIdx int64
-	has, err := db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ? AND max_index=?", repoID, sha, 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("SELECT max_index FROM `commit_status_index` WHERE repo_id=? AND sha=?", repoID, sha).Get(&newIdx)
 	if err != nil {
 		return 0, err
 	}
 	if !has {
-		return 0, db.ErrResouceOutdated
-	}
-	if err := commiter.Commit(); err != nil {
-		return 0, err
+		return 0, errors.New("impossible error when GetNextCommitStatusIndex, upsert succeeded but no record can be selected")
 	}
-	return curIdx, nil
+	return newIdx, nil
 }
 
 func (status *CommitStatus) loadAttributes(ctx context.Context) (err error) {
 	if status.Repo == nil {
 		status.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, status.RepoID)
 		if err != nil {
-			return fmt.Errorf("getRepositoryByID [%d]: %v", status.RepoID, err)
+			return fmt.Errorf("getRepositoryByID [%d]: %w", status.RepoID, err)
 		}
 	}
 	if status.Creator == nil && status.CreatorID > 0 {
 		status.Creator, err = user_model.GetUserByIDCtx(ctx, status.CreatorID)
 		if err != nil {
-			return fmt.Errorf("getUserByID [%d]: %v", status.CreatorID, err)
+			return fmt.Errorf("getUserByID [%d]: %w", status.CreatorID, err)
 		}
 	}
 	return nil
@@ -291,18 +281,22 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
 		return fmt.Errorf("NewCommitStatus[%s, %s]: no user specified", repoPath, opts.SHA)
 	}
 
-	// Get the next Status Index
-	idx, err := GetNextCommitStatusIndex(opts.Repo.ID, opts.SHA)
-	if err != nil {
-		return fmt.Errorf("generate commit status index failed: %v", err)
+	if _, err := git.NewIDFromString(opts.SHA); err != nil {
+		return fmt.Errorf("NewCommitStatus[%s, %s]: invalid sha: %w", repoPath, opts.SHA, err)
 	}
 
 	ctx, committer, err := db.TxContext()
 	if err != nil {
-		return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %v", opts.Repo.ID, opts.Creator.ID, opts.SHA, err)
+		return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", opts.Repo.ID, opts.Creator.ID, opts.SHA, err)
 	}
 	defer committer.Close()
 
+	// Get the next Status Index
+	idx, err := GetNextCommitStatusIndex(ctx, opts.Repo.ID, opts.SHA)
+	if err != nil {
+		return fmt.Errorf("generate commit status index failed: %w", err)
+	}
+
 	opts.CommitStatus.Description = strings.TrimSpace(opts.CommitStatus.Description)
 	opts.CommitStatus.Context = strings.TrimSpace(opts.CommitStatus.Context)
 	opts.CommitStatus.TargetURL = strings.TrimSpace(opts.CommitStatus.TargetURL)
@@ -316,7 +310,7 @@ func NewCommitStatus(opts NewCommitStatusOptions) error {
 
 	// Insert new CommitStatus
 	if _, err = db.GetEngine(ctx).Insert(opts.CommitStatus); err != nil {
-		return fmt.Errorf("Insert CommitStatus[%s, %s]: %v", repoPath, opts.SHA, err)
+		return fmt.Errorf("insert CommitStatus[%s, %s]: %w", repoPath, opts.SHA, err)
 	}
 
 	return committer.Commit()
diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go
index 9919297430063..7b81b1549c225 100644
--- a/models/git/commit_status_test.go
+++ b/models/git/commit_status_test.go
@@ -19,7 +19,7 @@ import (
 func TestGetCommitStatuses(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	sha1 := "1234123412341234123412341234123412341234"
 
diff --git a/models/git/lfs.go b/models/git/lfs.go
index ec963cf593582..58042edfdbe1f 100644
--- a/models/git/lfs.go
+++ b/models/git/lfs.go
@@ -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.
@@ -278,29 +294,6 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *user_model.User, repoID int6
 	return committer.Commit()
 }
 
-// IterateLFS iterates lfs object
-func IterateLFS(f func(mo *LFSMetaObject) error) error {
-	var start int
-	const batchSize = 100
-	e := db.GetEngine(db.DefaultContext)
-	for {
-		mos := make([]*LFSMetaObject, 0, batchSize)
-		if err := e.Limit(batchSize, start).Find(&mos); err != nil {
-			return err
-		}
-		if len(mos) == 0 {
-			return nil
-		}
-		start += len(mos)
-
-		for _, mo := range mos {
-			if err := f(mo); err != nil {
-				return err
-			}
-		}
-	}
-}
-
 // CopyLFS copies LFS data from one repo to another
 func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error {
 	var lfsObjects []*LFSMetaObject
@@ -323,7 +316,7 @@ func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error
 func GetRepoLFSSize(ctx context.Context, repoID int64) (int64, error) {
 	lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repoID).SumInt(new(LFSMetaObject), "size")
 	if err != nil {
-		return 0, fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err)
+		return 0, fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err)
 	}
 	return lfsSize, nil
 }
diff --git a/models/issues/assignees.go b/models/issues/assignees.go
index 5921112feaf4b..d960d5ebafed6 100644
--- a/models/issues/assignees.go
+++ b/models/issues/assignees.go
@@ -42,7 +42,7 @@ func (issue *Issue) LoadAssignees(ctx context.Context) (err error) {
 	if len(issue.Assignees) > 0 {
 		issue.Assignee = issue.Assignees[0]
 	}
-	return
+	return err
 }
 
 // GetAssigneeIDsByIssue returns the IDs of users assigned to an issue
@@ -85,12 +85,12 @@ func ToggleIssueAssignee(issue *Issue, doer *user_model.User, assigneeID int64)
 func toggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.User, assigneeID int64, isCreate bool) (removed bool, comment *Comment, err error) {
 	removed, err = toggleUserAssignee(ctx, issue, assigneeID)
 	if err != nil {
-		return false, nil, fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
+		return false, nil, fmt.Errorf("UpdateIssueUserByAssignee: %w", err)
 	}
 
 	// Repo infos
 	if err = issue.LoadRepo(ctx); err != nil {
-		return false, nil, fmt.Errorf("loadRepo: %v", err)
+		return false, nil, fmt.Errorf("loadRepo: %w", err)
 	}
 
 	opts := &CreateCommentOptions{
@@ -104,7 +104,7 @@ func toggleIssueAssignee(ctx context.Context, issue *Issue, doer *user_model.Use
 	// Comment
 	comment, err = CreateCommentCtx(ctx, opts)
 	if err != nil {
-		return false, nil, fmt.Errorf("createComment: %v", err)
+		return false, nil, fmt.Errorf("createComment: %w", err)
 	}
 
 	// if pull request is in the middle of creation - don't call webhook
@@ -167,5 +167,5 @@ func MakeIDsFromAPIAssigneesToAdd(oneAssignee string, multipleAssignees []string
 	// Get the IDs of all assignees
 	assigneeIDs, err = user_model.GetUserIDsByNames(requestAssignees, false)
 
-	return
+	return assigneeIDs, err
 }
diff --git a/models/issues/assignees_test.go b/models/issues/assignees_test.go
index 37d966f140f74..291bb673da84b 100644
--- a/models/issues/assignees_test.go
+++ b/models/issues/assignees_test.go
@@ -68,8 +68,8 @@ func TestUpdateAssignee(t *testing.T) {
 func TestMakeIDsFromAPIAssigneesToAdd(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	_ = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	IDs, err := issues_model.MakeIDsFromAPIAssigneesToAdd("", []string{""})
 	assert.NoError(t, err)
diff --git a/models/issues/comment.go b/models/issues/comment.go
index a4e69e7118f34..6877991a9399c 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -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
 
@@ -315,7 +320,7 @@ func (c *Comment) LoadIssueCtx(ctx context.Context) (err error) {
 		return nil
 	}
 	c.Issue, err = GetIssueByID(ctx, c.IssueID)
-	return
+	return err
 }
 
 // BeforeInsert will be invoked by XORM before inserting a record
@@ -568,13 +573,13 @@ func (c *Comment) UpdateAttachments(uuids []string) error {
 
 	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
 	if err != nil {
-		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
+		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
 	}
 	for i := 0; i < len(attachments); i++ {
 		attachments[i].IssueID = c.IssueID
 		attachments[i].CommentID = c.ID
 		if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
-			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
+			return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
 		}
 	}
 	return committer.Commit()
@@ -627,7 +632,7 @@ func (c *Comment) LoadResolveDoer() (err error) {
 			err = nil
 		}
 	}
-	return
+	return err
 }
 
 // IsResolved check if an code comment is resolved
@@ -869,7 +874,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 		// Check attachments
 		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
 		if err != nil {
-			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err)
+			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
 		}
 
 		for i := range attachments {
@@ -877,11 +882,11 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 			attachments[i].CommentID = comment.ID
 			// No assign value could be 0, so ignore AllCols().
 			if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
-				return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
+				return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
 			}
 		}
 	case CommentTypeReopen, CommentTypeClose:
-		if err = updateIssueClosedNum(ctx, opts.Issue); err != nil {
+		if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
 			return err
 		}
 	}
@@ -955,7 +960,7 @@ func createIssueDependencyComment(ctx context.Context, doer *user_model.User, is
 		DependentIssueID: issue.ID,
 	}
 	_, err = CreateCommentCtx(ctx, opts)
-	return
+	return err
 }
 
 // CreateCommentOptions defines options for creating comment
@@ -1029,7 +1034,7 @@ func CreateRefComment(doer *user_model.User, repo *repo_model.Repository, issue
 		CommitSHA: commitSHA,
 	})
 	if err != nil {
-		return fmt.Errorf("check reference comment: %v", err)
+		return fmt.Errorf("check reference comment: %w", err)
 	} else if has {
 		return nil
 	}
@@ -1147,7 +1152,7 @@ func UpdateComment(c *Comment, doer *user_model.User) error {
 		return err
 	}
 	if err := committer.Commit(); err != nil {
-		return fmt.Errorf("Commit: %v", err)
+		return fmt.Errorf("Commit: %w", err)
 	}
 
 	return nil
@@ -1350,7 +1355,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *Pul
 
 	comment, err = CreateComment(ops)
 
-	return
+	return comment, err
 }
 
 // CreateAutoMergeComment is a internal function, only use it for CommentTypePRScheduledToAutoMerge and CommentTypePRUnScheduledToAutoMerge CommentTypes
@@ -1372,7 +1377,7 @@ func CreateAutoMergeComment(ctx context.Context, typ CommentType, pr *PullReques
 		Repo:  pr.BaseRepo,
 		Issue: pr.Issue,
 	})
-	return
+	return comment, err
 }
 
 // getCommitsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
@@ -1434,7 +1439,7 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC
 		}
 	}
 
-	return
+	return commitIDs, isForcePush, err
 }
 
 type commitBranchCheckItem struct {
diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go
index e3406a5cbe08f..70105d7ff056e 100644
--- a/models/issues/comment_list.go
+++ b/models/issues/comment_list.go
@@ -17,13 +17,11 @@ import (
 type CommentList []*Comment
 
 func (comments CommentList) getPosterIDs() []int64 {
-	posterIDs := make(map[int64]struct{}, len(comments))
+	posterIDs := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := posterIDs[comment.PosterID]; !ok {
-			posterIDs[comment.PosterID] = struct{}{}
-		}
+		posterIDs.Add(comment.PosterID)
 	}
-	return container.KeysInt64(posterIDs)
+	return posterIDs.Values()
 }
 
 func (comments CommentList) loadPosters(ctx context.Context) error {
@@ -70,13 +68,11 @@ func (comments CommentList) getCommentIDs() []int64 {
 }
 
 func (comments CommentList) getLabelIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.LabelID]; !ok {
-			ids[comment.LabelID] = struct{}{}
-		}
+		ids.Add(comment.LabelID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
@@ -120,13 +116,11 @@ func (comments CommentList) loadLabels(ctx context.Context) error { //nolint
 }
 
 func (comments CommentList) getMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.MilestoneID]; !ok {
-			ids[comment.MilestoneID] = struct{}{}
-		}
+		ids.Add(comment.MilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadMilestones(ctx context.Context) error {
@@ -163,13 +157,11 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
 }
 
 func (comments CommentList) getOldMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.OldMilestoneID]; !ok {
-			ids[comment.OldMilestoneID] = struct{}{}
-		}
+		ids.Add(comment.OldMilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadOldMilestones(ctx context.Context) error {
@@ -206,13 +198,11 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
 }
 
 func (comments CommentList) getAssigneeIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.AssigneeID]; !ok {
-			ids[comment.AssigneeID] = struct{}{}
-		}
+		ids.Add(comment.AssigneeID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadAssignees(ctx context.Context) error {
@@ -259,16 +249,14 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
 
 // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded
 func (comments CommentList) getIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
 		if comment.Issue != nil {
 			continue
 		}
-		if _, ok := ids[comment.IssueID]; !ok {
-			ids[comment.IssueID] = struct{}{}
-		}
+		ids.Add(comment.IssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 // Issues returns all the issues of comments
@@ -334,16 +322,14 @@ func (comments CommentList) loadIssues(ctx context.Context) error {
 }
 
 func (comments CommentList) getDependentIssueIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
 		if comment.DependentIssue != nil {
 			continue
 		}
-		if _, ok := ids[comment.DependentIssueID]; !ok {
-			ids[comment.DependentIssueID] = struct{}{}
-		}
+		ids.Add(comment.DependentIssueID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadDependentIssues(ctx context.Context) error {
@@ -439,13 +425,11 @@ func (comments CommentList) loadAttachments(ctx context.Context) (err error) {
 }
 
 func (comments CommentList) getReviewIDs() []int64 {
-	ids := make(map[int64]struct{}, len(comments))
+	ids := make(container.Set[int64], len(comments))
 	for _, comment := range comments {
-		if _, ok := ids[comment.ReviewID]; !ok {
-			ids[comment.ReviewID] = struct{}{}
-		}
+		ids.Add(comment.ReviewID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (comments CommentList) loadReviews(ctx context.Context) error { //nolint
diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go
index 06b0b85e3cff5..f12da0177f86a 100644
--- a/models/issues/comment_test.go
+++ b/models/issues/comment_test.go
@@ -20,9 +20,9 @@ import (
 func TestCreateComment(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}).(*issues_model.Issue)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository)
-	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
+	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 
 	now := time.Now().Unix()
 	comment, err := issues_model.CreateComment(&issues_model.CreateCommentOptions{
@@ -42,15 +42,15 @@ func TestCreateComment(t *testing.T) {
 	unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix))
 	unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB
 
-	updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}).(*issues_model.Issue)
+	updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
 	unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
 }
 
 func TestFetchCodeComments(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}).(*issues_model.Issue)
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 	res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user)
 	assert.NoError(t, err)
 	assert.Contains(t, res, "README.md")
@@ -58,7 +58,7 @@ func TestFetchCodeComments(t *testing.T) {
 	assert.Len(t, res["README.md"][4], 1)
 	assert.Equal(t, int64(4), res["README.md"][4][0].ID)
 
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2)
 	assert.NoError(t, err)
 	assert.Len(t, res, 1)
diff --git a/models/issues/content_history.go b/models/issues/content_history.go
index 3e321784bd0a8..f5cfa65b8ff4f 100644
--- a/models/issues/content_history.go
+++ b/models/issues/content_history.go
@@ -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{}
diff --git a/models/issues/dependency.go b/models/issues/dependency.go
index d664c0758e77e..4754ed0f5f95e 100644
--- a/models/issues/dependency.go
+++ b/models/issues/dependency.go
@@ -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"`
diff --git a/models/issues/issue.go b/models/issues/issue.go
index 76a0ea7d0cb55..b9aa649d213ad 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -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,13 +20,13 @@ 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"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/references"
-	"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"
@@ -53,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
@@ -193,7 +196,7 @@ func (issue *Issue) LoadRepo(ctx context.Context) (err error) {
 	if issue.Repo == nil {
 		issue.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, issue.RepoID)
 		if err != nil {
-			return fmt.Errorf("getRepositoryByID [%d]: %v", issue.RepoID, err)
+			return fmt.Errorf("getRepositoryByID [%d]: %w", issue.RepoID, err)
 		}
 	}
 	return nil
@@ -223,7 +226,7 @@ func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
 		return nil, err
 	}
 	pr.Issue = issue
-	return
+	return pr, err
 }
 
 // LoadLabels loads labels
@@ -231,7 +234,7 @@ func (issue *Issue) LoadLabels(ctx context.Context) (err error) {
 	if issue.Labels == nil {
 		issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
 		if err != nil {
-			return fmt.Errorf("getLabelsByIssueID [%d]: %v", issue.ID, err)
+			return fmt.Errorf("getLabelsByIssueID [%d]: %w", issue.ID, err)
 		}
 	}
 	return nil
@@ -249,13 +252,13 @@ func (issue *Issue) loadPoster(ctx context.Context) (err error) {
 			issue.PosterID = -1
 			issue.Poster = user_model.NewGhostUser()
 			if !user_model.IsErrUserNotExist(err) {
-				return fmt.Errorf("getUserByID.(poster) [%d]: %v", issue.PosterID, err)
+				return fmt.Errorf("getUserByID.(poster) [%d]: %w", issue.PosterID, err)
 			}
 			err = nil
 			return
 		}
 	}
-	return
+	return err
 }
 
 func (issue *Issue) loadPullRequest(ctx context.Context) (err error) {
@@ -265,7 +268,7 @@ func (issue *Issue) loadPullRequest(ctx context.Context) (err error) {
 			if IsErrPullRequestNotExist(err) {
 				return err
 			}
-			return fmt.Errorf("getPullRequestByIssueID [%d]: %v", issue.ID, err)
+			return fmt.Errorf("getPullRequestByIssueID [%d]: %w", issue.ID, err)
 		}
 		issue.PullRequest.Issue = issue
 	}
@@ -311,7 +314,7 @@ func (issue *Issue) loadReactions(ctx context.Context) (err error) {
 		return err
 	}
 	// Load reaction user data
-	if _, err := ReactionList(reactions).LoadUsers(ctx, issue.Repo); err != nil {
+	if _, err := reactions.LoadUsers(ctx, issue.Repo); err != nil {
 		return err
 	}
 
@@ -358,7 +361,7 @@ func (issue *Issue) loadMilestone(ctx context.Context) (err error) {
 	if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
 		issue.Milestone, err = GetMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID)
 		if err != nil && !IsErrMilestoneNotExist(err) {
-			return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
+			return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %w", issue.RepoID, issue.MilestoneID, err)
 		}
 	}
 	return nil
@@ -398,7 +401,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
 	if issue.Attachments == nil {
 		issue.Attachments, err = repo_model.GetAttachmentsByIssueID(ctx, issue.ID)
 		if err != nil {
-			return fmt.Errorf("getAttachmentsByIssueID [%d]: %v", issue.ID, err)
+			return fmt.Errorf("getAttachmentsByIssueID [%d]: %w", issue.ID, err)
 		}
 	}
 
@@ -515,19 +518,19 @@ func (issue *Issue) getLabels(ctx context.Context) (err error) {
 
 	issue.Labels, err = GetLabelsByIssueID(ctx, issue.ID)
 	if err != nil {
-		return fmt.Errorf("getLabelsByIssueID: %v", err)
+		return fmt.Errorf("getLabelsByIssueID: %w", err)
 	}
 	return nil
 }
 
 func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) {
 	if err = issue.getLabels(ctx); err != nil {
-		return fmt.Errorf("getLabels: %v", err)
+		return fmt.Errorf("getLabels: %w", err)
 	}
 
 	for i := range issue.Labels {
 		if err = deleteIssueLabel(ctx, issue, issue.Labels[i], doer); err != nil {
-			return fmt.Errorf("removeLabel: %v", err)
+			return fmt.Errorf("removeLabel: %w", err)
 		}
 	}
 
@@ -562,7 +565,7 @@ func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) {
 	}
 
 	if err = committer.Commit(); err != nil {
-		return fmt.Errorf("Commit: %v", err)
+		return fmt.Errorf("Commit: %w", err)
 	}
 
 	return nil
@@ -632,13 +635,13 @@ func ReplaceIssueLabels(issue *Issue, labels []*Label, doer *user_model.User) (e
 
 	if len(toAdd) > 0 {
 		if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
-			return fmt.Errorf("addLabels: %v", err)
+			return fmt.Errorf("addLabels: %w", err)
 		}
 	}
 
 	for _, l := range toRemove {
 		if err = deleteIssueLabel(ctx, issue, l, doer); err != nil {
-			return fmt.Errorf("removeLabel: %v", err)
+			return fmt.Errorf("removeLabel: %w", err)
 		}
 	}
 
@@ -722,7 +725,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
 		}
 	}
 
-	if err := updateIssueClosedNum(ctx, issue); err != nil {
+	// update repository's issue closed number
+	if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
 		return nil, err
 	}
 
@@ -763,11 +767,11 @@ func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err
 	defer committer.Close()
 
 	if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
-		return fmt.Errorf("updateIssueCols: %v", err)
+		return fmt.Errorf("updateIssueCols: %w", err)
 	}
 
 	if err = issue.LoadRepo(ctx); err != nil {
-		return fmt.Errorf("loadRepo: %v", err)
+		return fmt.Errorf("loadRepo: %w", err)
 	}
 
 	opts := &CreateCommentOptions{
@@ -779,7 +783,7 @@ func ChangeIssueTitle(issue *Issue, doer *user_model.User, oldTitle string) (err
 		NewTitle: issue.Title,
 	}
 	if _, err = CreateCommentCtx(ctx, opts); err != nil {
-		return fmt.Errorf("createComment: %v", err)
+		return fmt.Errorf("createComment: %w", err)
 	}
 	if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
 		return err
@@ -797,11 +801,11 @@ func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err err
 	defer committer.Close()
 
 	if err = UpdateIssueCols(ctx, issue, "ref"); err != nil {
-		return fmt.Errorf("updateIssueCols: %v", err)
+		return fmt.Errorf("updateIssueCols: %w", err)
 	}
 
 	if err = issue.LoadRepo(ctx); err != nil {
-		return fmt.Errorf("loadRepo: %v", err)
+		return fmt.Errorf("loadRepo: %w", err)
 	}
 	oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix)
 	newRefFriendly := strings.TrimPrefix(issue.Ref, git.BranchPrefix)
@@ -815,7 +819,7 @@ func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err err
 		NewRef: newRefFriendly,
 	}
 	if _, err = CreateCommentCtx(ctx, opts); err != nil {
-		return fmt.Errorf("createComment: %v", err)
+		return fmt.Errorf("createComment: %w", err)
 	}
 
 	return committer.Commit()
@@ -847,12 +851,12 @@ func UpdateIssueAttachments(issueID int64, uuids []string) (err error) {
 	defer committer.Close()
 	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
 	if err != nil {
-		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
+		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
 	}
 	for i := 0; i < len(attachments); i++ {
 		attachments[i].IssueID = issueID
 		if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
-			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
+			return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
 		}
 	}
 	return committer.Commit()
@@ -868,28 +872,28 @@ func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (er
 
 	hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0)
 	if err != nil {
-		return fmt.Errorf("HasIssueContentHistory: %v", err)
+		return fmt.Errorf("HasIssueContentHistory: %w", err)
 	}
 	if !hasContentHistory {
 		if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0,
 			issue.CreatedUnix, issue.Content, true); err != nil {
-			return fmt.Errorf("SaveIssueContentHistory: %v", err)
+			return fmt.Errorf("SaveIssueContentHistory: %w", err)
 		}
 	}
 
 	issue.Content = content
 
 	if err = UpdateIssueCols(ctx, issue, "content"); err != nil {
-		return fmt.Errorf("UpdateIssueCols: %v", err)
+		return fmt.Errorf("UpdateIssueCols: %w", err)
 	}
 
 	if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
 		timeutil.TimeStampNow(), issue.Content, false); err != nil {
-		return fmt.Errorf("SaveIssueContentHistory: %v", err)
+		return fmt.Errorf("SaveIssueContentHistory: %w", err)
 	}
 
 	if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
-		return fmt.Errorf("addCrossReferences: %v", err)
+		return fmt.Errorf("addCrossReferences: %w", err)
 	}
 
 	return committer.Commit()
@@ -966,7 +970,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
 	if opts.Issue.MilestoneID > 0 {
 		milestone, err := GetMilestoneByRepoID(ctx, opts.Issue.RepoID, opts.Issue.MilestoneID)
 		if err != nil && !IsErrMilestoneNotExist(err) {
-			return fmt.Errorf("getMilestoneByID: %v", err)
+			return fmt.Errorf("getMilestoneByID: %w", err)
 		}
 
 		// Assume milestone is invalid and drop silently.
@@ -1006,12 +1010,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
 		}
 	}
 
-	if opts.IsPull {
-		_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
-	} else {
-		_, err = e.Exec("UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?", opts.Issue.RepoID)
-	}
-	if err != nil {
+	if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
 		return err
 	}
 
@@ -1020,7 +1019,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
 		// So we have to get all needed labels first.
 		labels := make([]*Label, 0, len(opts.LabelIDs))
 		if err = e.In("id", opts.LabelIDs).Find(&labels); err != nil {
-			return fmt.Errorf("find all labels [label_ids: %v]: %v", opts.LabelIDs, err)
+			return fmt.Errorf("find all labels [label_ids: %v]: %w", opts.LabelIDs, err)
 		}
 
 		if err = opts.Issue.loadPoster(ctx); err != nil {
@@ -1034,7 +1033,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
 			}
 
 			if err = newIssueLabel(ctx, opts.Issue, label, opts.Issue.Poster); err != nil {
-				return fmt.Errorf("addLabel [id: %d]: %v", label.ID, err)
+				return fmt.Errorf("addLabel [id: %d]: %w", label.ID, err)
 			}
 		}
 	}
@@ -1046,13 +1045,13 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
 	if len(opts.Attachments) > 0 {
 		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
 		if err != nil {
-			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err)
+			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
 		}
 
 		for i := 0; i < len(attachments); i++ {
 			attachments[i].IssueID = opts.Issue.ID
 			if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
-				return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
+				return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
 			}
 		}
 	}
@@ -1065,19 +1064,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,
@@ -1087,11 +1086,11 @@ func NewIssue(repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids
 		if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
 			return err
 		}
-		return fmt.Errorf("newIssue: %v", err)
+		return fmt.Errorf("newIssue: %w", err)
 	}
 
 	if err = committer.Commit(); err != nil {
-		return fmt.Errorf("Commit: %v", err)
+		return fmt.Errorf("Commit: %w", err)
 	}
 
 	return nil
@@ -1187,6 +1186,7 @@ type IssuesOptions struct { //nolint
 	PosterID           int64
 	MentionedID        int64
 	ReviewRequestedID  int64
+	SubscriberID       int64
 	MilestoneIDs       []int64
 	ProjectID          int64
 	ProjectBoardID     int64
@@ -1300,6 +1300,10 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
 		applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
 	}
 
+	if opts.SubscriberID > 0 {
+		applySubscribedCondition(sess, opts.SubscriberID)
+	}
+
 	if len(opts.MilestoneIDs) > 0 {
 		sess.In("issue.milestone_id", opts.MilestoneIDs)
 	}
@@ -1464,6 +1468,37 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
 			reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID)
 }
 
+func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
+	return sess.And(
+		builder.
+			NotIn("issue.id",
+				builder.Select("issue_id").
+					From("issue_watch").
+					Where(builder.Eq{"is_watching": false, "user_id": subscriberID}),
+			),
+	).And(
+		builder.Or(
+			builder.In("issue.id", builder.
+				Select("issue_id").
+				From("issue_watch").
+				Where(builder.Eq{"is_watching": true, "user_id": subscriberID}),
+			),
+			builder.In("issue.id", builder.
+				Select("issue_id").
+				From("comment").
+				Where(builder.Eq{"poster_id": subscriberID}),
+			),
+			builder.Eq{"issue.poster_id": subscriberID},
+			builder.In("issue.repo_id", builder.
+				Select("id").
+				From("watch").
+				Where(builder.And(builder.Eq{"user_id": subscriberID},
+					builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
+			),
+		),
+	)
+}
+
 // CountIssuesByRepo map from repoID to number of issues matching the options
 func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
 	e := db.GetEngine(db.DefaultContext)
@@ -1575,7 +1610,7 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
 		ids[i] = u.ID
 	}
 	if err := UpdateIssueUsersByMentions(ctx, issueID, ids); err != nil {
-		return fmt.Errorf("UpdateIssueUsersByMentions: %v", err)
+		return fmt.Errorf("UpdateIssueUsersByMentions: %w", err)
 	}
 	return nil
 }
@@ -1903,23 +1938,17 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen,
 func SearchIssueIDsByKeyword(ctx context.Context, kw string, repoIDs []int64, limit, start int) (int64, []int64, error) {
 	repoCond := builder.In("repo_id", repoIDs)
 	subQuery := builder.Select("id").From("issue").Where(repoCond)
-	// SQLite's UPPER function only transforms ASCII letters.
-	if setting.Database.UseSQLite3 {
-		kw = util.ToUpperASCII(kw)
-	} else {
-		kw = strings.ToUpper(kw)
-	}
 	cond := builder.And(
 		repoCond,
 		builder.Or(
-			builder.Like{"UPPER(name)", kw},
-			builder.Like{"UPPER(content)", kw},
+			db.BuildCaseInsensitiveLike("name", kw),
+			db.BuildCaseInsensitiveLike("content", kw),
 			builder.In("id", builder.Select("issue_id").
 				From("comment").
 				Where(builder.And(
 					builder.Eq{"type": CommentTypeComment},
 					builder.In("issue_id", subQuery),
-					builder.Like{"UPPER(content)", kw},
+					db.BuildCaseInsensitiveLike("content", kw),
 				)),
 			),
 		),
@@ -1959,7 +1988,7 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment
 	defer committer.Close()
 
 	if err := issue.LoadRepo(ctx); err != nil {
-		return nil, false, fmt.Errorf("loadRepo: %v", err)
+		return nil, false, fmt.Errorf("loadRepo: %w", err)
 	}
 
 	// Reload the issue
@@ -1987,7 +2016,7 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment
 		}
 		_, err := CreateCommentCtx(ctx, opts)
 		if err != nil {
-			return nil, false, fmt.Errorf("createComment: %v", err)
+			return nil, false, fmt.Errorf("createComment: %w", err)
 		}
 	}
 
@@ -2023,7 +2052,7 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *us
 
 	// Make the comment
 	if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil {
-		return fmt.Errorf("createRemovedDueDateComment: %v", err)
+		return fmt.Errorf("createRemovedDueDateComment: %w", err)
 	}
 
 	return committer.Commit()
@@ -2060,7 +2089,7 @@ func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, erro
 		Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
 		Distinct("poster_id").
 		Find(&userIDs); err != nil {
-		return nil, fmt.Errorf("get poster IDs: %v", err)
+		return nil, fmt.Errorf("get poster IDs: %w", err)
 	}
 	if !util.IsInt64InSlice(issue.PosterID, userIDs) {
 		return append(userIDs, issue.PosterID), nil
@@ -2104,26 +2133,17 @@ func (issue *Issue) BlockingDependencies(ctx context.Context) (issueDeps []*Depe
 	return issueDeps, err
 }
 
-func updateIssueClosedNum(ctx context.Context, issue *Issue) (err error) {
-	if issue.IsPull {
-		err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, true, "num_closed_pulls")
-	} else {
-		err = repo_model.StatsCorrectNumClosed(ctx, issue.RepoID, false, "num_closed_issues")
-	}
-	return
-}
-
 // FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
 func FindAndUpdateIssueMentions(ctx context.Context, issue *Issue, doer *user_model.User, content string) (mentions []*user_model.User, err error) {
 	rawMentions := references.FindAllMentionsMarkdown(content)
 	mentions, err = ResolveIssueMentionsByVisibility(ctx, issue, doer, rawMentions)
 	if err != nil {
-		return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
+		return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
 	}
 	if err = UpdateIssueMentions(ctx, issue.ID, mentions); err != nil {
-		return nil, fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
+		return nil, fmt.Errorf("UpdateIssueMentions [%d]: %w", issue.ID, err)
 	}
-	return
+	return mentions, err
 }
 
 // ResolveIssueMentionsByVisibility returns the users mentioned in an issue, removing those that
@@ -2173,7 +2193,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 			Where("team_repo.repo_id=?", issue.Repo.ID).
 			In("team.lower_name", mentionTeams).
 			Find(&teams); err != nil {
-			return nil, fmt.Errorf("find mentioned teams: %v", err)
+			return nil, fmt.Errorf("find mentioned teams: %w", err)
 		}
 		if len(teams) != 0 {
 			checked := make([]int64, 0, len(teams))
@@ -2189,7 +2209,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 				}
 				has, err := db.GetEngine(ctx).Get(&organization.TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype})
 				if err != nil {
-					return nil, fmt.Errorf("get team units (%d): %v", team.ID, err)
+					return nil, fmt.Errorf("get team units (%d): %w", team.ID, err)
 				}
 				if has {
 					checked = append(checked, team.ID)
@@ -2204,7 +2224,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 					And("`user`.is_active = ?", true).
 					And("`user`.prohibit_login = ?", false).
 					Find(&teamusers); err != nil {
-					return nil, fmt.Errorf("get teams users: %v", err)
+					return nil, fmt.Errorf("get teams users: %w", err)
 				}
 				if len(teamusers) > 0 {
 					users = make([]*user_model.User, 0, len(teamusers))
@@ -2240,7 +2260,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 		And("`user`.prohibit_login = ?", false).
 		In("`user`.lower_name", mentionUsers).
 		Find(&unchecked); err != nil {
-		return nil, fmt.Errorf("find mentioned users: %v", err)
+		return nil, fmt.Errorf("find mentioned users: %w", err)
 	}
 	for _, user := range unchecked {
 		if already := resolved[user.LowerName]; already || user.IsOrganization() {
@@ -2249,7 +2269,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 		// Normal users must have read access to the referencing issue
 		perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, user)
 		if err != nil {
-			return nil, fmt.Errorf("GetUserRepoPermission [%d]: %v", user.ID, err)
+			return nil, fmt.Errorf("GetUserRepoPermission [%d]: %w", user.ID, err)
 		}
 		if !perm.CanReadIssuesOrPulls(issue.IsPull) {
 			continue
@@ -2257,7 +2277,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 		users = append(users, user)
 	}
 
-	return
+	return users, err
 }
 
 // UpdateIssuesMigrationsByType updates all migrated repositories' issues from gitServiceType to replace originalAuthorID to posterID
@@ -2380,7 +2400,7 @@ func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []
 		return
 	}
 
-	return
+	return attachmentPaths, err
 }
 
 // RemapExternalUser ExternalUserRemappable interface
@@ -2442,7 +2462,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
 }
diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go
index 100e8143178ab..f4acc5aa1bc0d 100644
--- a/models/issues/issue_index.go
+++ b/models/issues/issue_index.go
@@ -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
 	}
 
diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go
index 20e9949b66202..bbe2292dd1de0 100644
--- a/models/issues/issue_list.go
+++ b/models/issues/issue_list.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	project_model "code.gitea.io/gitea/models/project"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/container"
@@ -21,16 +22,16 @@ type IssueList []*Issue
 
 // get the repo IDs to be loaded later, these IDs are for issue.Repo and issue.PullRequest.HeadRepo
 func (issues IssueList) getRepoIDs() []int64 {
-	repoIDs := make(map[int64]struct{}, len(issues))
+	repoIDs := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
 		if issue.Repo == nil {
-			repoIDs[issue.RepoID] = struct{}{}
+			repoIDs.Add(issue.RepoID)
 		}
 		if issue.PullRequest != nil && issue.PullRequest.HeadRepo == nil {
-			repoIDs[issue.PullRequest.HeadRepoID] = struct{}{}
+			repoIDs.Add(issue.PullRequest.HeadRepoID)
 		}
 	}
-	return container.KeysInt64(repoIDs)
+	return repoIDs.Values()
 }
 
 func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Repository, error) {
@@ -50,7 +51,7 @@ func (issues IssueList) loadRepositories(ctx context.Context) ([]*repo_model.Rep
 			In("id", repoIDs[:limit]).
 			Find(&repoMaps)
 		if err != nil {
-			return nil, fmt.Errorf("find repository: %v", err)
+			return nil, fmt.Errorf("find repository: %w", err)
 		}
 		left -= limit
 		repoIDs = repoIDs[limit:]
@@ -78,13 +79,11 @@ func (issues IssueList) LoadRepositories() ([]*repo_model.Repository, error) {
 }
 
 func (issues IssueList) getPosterIDs() []int64 {
-	posterIDs := make(map[int64]struct{}, len(issues))
+	posterIDs := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
-		if _, ok := posterIDs[issue.PosterID]; !ok {
-			posterIDs[issue.PosterID] = struct{}{}
-		}
+		posterIDs.Add(issue.PosterID)
 	}
-	return container.KeysInt64(posterIDs)
+	return posterIDs.Values()
 }
 
 func (issues IssueList) loadPosters(ctx context.Context) error {
@@ -162,7 +161,7 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
 			err = rows.Scan(&labelIssue)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadLabels: Close: %w", err1)
 				}
 				return err
 			}
@@ -171,7 +170,7 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
 		// When there are no rows left and we try to close it.
 		// Since that is not relevant for us, we can safely ignore it.
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadLabels: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadLabels: Close: %w", err1)
 		}
 		left -= limit
 		issueIDs = issueIDs[limit:]
@@ -184,13 +183,11 @@ func (issues IssueList) loadLabels(ctx context.Context) error {
 }
 
 func (issues IssueList) getMilestoneIDs() []int64 {
-	ids := make(map[int64]struct{}, len(issues))
+	ids := make(container.Set[int64], len(issues))
 	for _, issue := range issues {
-		if _, ok := ids[issue.MilestoneID]; !ok {
-			ids[issue.MilestoneID] = struct{}{}
-		}
+		ids.Add(issue.MilestoneID)
 	}
-	return container.KeysInt64(ids)
+	return ids.Values()
 }
 
 func (issues IssueList) loadMilestones(ctx context.Context) error {
@@ -222,6 +219,43 @@ func (issues IssueList) loadMilestones(ctx context.Context) error {
 	return nil
 }
 
+func (issues IssueList) getProjectIDs() []int64 {
+	ids := make(container.Set[int64], len(issues))
+	for _, issue := range issues {
+		ids.Add(issue.ProjectID())
+	}
+	return ids.Values()
+}
+
+func (issues IssueList) loadProjects(ctx context.Context) error {
+	projectIDs := issues.getProjectIDs()
+	if len(projectIDs) == 0 {
+		return nil
+	}
+
+	projectMaps := make(map[int64]*project_model.Project, len(projectIDs))
+	left := len(projectIDs)
+	for left > 0 {
+		limit := db.DefaultMaxInSize
+		if left < limit {
+			limit = left
+		}
+		err := db.GetEngine(ctx).
+			In("id", projectIDs[:limit]).
+			Find(&projectMaps)
+		if err != nil {
+			return err
+		}
+		left -= limit
+		projectIDs = projectIDs[limit:]
+	}
+
+	for _, issue := range issues {
+		issue.Project = projectMaps[issue.ProjectID()]
+	}
+	return nil
+}
+
 func (issues IssueList) loadAssignees(ctx context.Context) error {
 	if len(issues) == 0 {
 		return nil
@@ -242,7 +276,7 @@ func (issues IssueList) loadAssignees(ctx context.Context) error {
 		}
 		rows, err := db.GetEngine(ctx).Table("issue_assignees").
 			Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
-			In("`issue_assignees`.issue_id", issueIDs[:limit]).
+			In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
 			Rows(new(AssigneeIssue))
 		if err != nil {
 			return err
@@ -253,7 +287,7 @@ func (issues IssueList) loadAssignees(ctx context.Context) error {
 			err = rows.Scan(&assigneeIssue)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
 				}
 				return err
 			}
@@ -261,7 +295,7 @@ func (issues IssueList) loadAssignees(ctx context.Context) error {
 			assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
 		}
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadAssignees: Close: %w", err1)
 		}
 		left -= limit
 		issueIDs = issueIDs[limit:]
@@ -308,14 +342,14 @@ func (issues IssueList) loadPullRequests(ctx context.Context) error {
 			err = rows.Scan(&pr)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
 				}
 				return err
 			}
 			pullRequestMaps[pr.IssueID] = &pr
 		}
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadPullRequests: Close: %w", err1)
 		}
 		left -= limit
 		issuesIDs = issuesIDs[limit:]
@@ -353,14 +387,14 @@ func (issues IssueList) loadAttachments(ctx context.Context) (err error) {
 			err = rows.Scan(&attachment)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
 				}
 				return err
 			}
 			attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
 		}
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadAttachments: Close: %w", err1)
 		}
 		left -= limit
 		issuesIDs = issuesIDs[limit:]
@@ -399,14 +433,14 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
 			err = rows.Scan(&comment)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
 				}
 				return err
 			}
 			comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
 		}
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadComments: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadComments: Close: %w", err1)
 		}
 		left -= limit
 		issuesIDs = issuesIDs[limit:]
@@ -458,14 +492,14 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
 			err = rows.Scan(&totalTime)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
-					return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
+					return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
 				}
 				return err
 			}
 			trackedTimes[totalTime.IssueID] = totalTime.Time
 		}
 		if err1 := rows.Close(); err1 != nil {
-			return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1)
+			return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %w", err1)
 		}
 		left -= limit
 		ids = ids[limit:]
@@ -480,31 +514,35 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
 // loadAttributes loads all attributes, expect for attachments and comments
 func (issues IssueList) loadAttributes(ctx context.Context) error {
 	if _, err := issues.loadRepositories(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadRepositories: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadRepositories: %w", err)
 	}
 
 	if err := issues.loadPosters(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadPosters: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadPosters: %w", err)
 	}
 
 	if err := issues.loadLabels(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadLabels: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadLabels: %w", err)
 	}
 
 	if err := issues.loadMilestones(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadMilestones: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadMilestones: %w", err)
+	}
+
+	if err := issues.loadProjects(ctx); err != nil {
+		return fmt.Errorf("issue.loadAttributes: loadProjects: %w", err)
 	}
 
 	if err := issues.loadAssignees(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadAssignees: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadAssignees: %w", err)
 	}
 
 	if err := issues.loadPullRequests(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadPullRequests: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadPullRequests: %w", err)
 	}
 
 	if err := issues.loadTotalTrackedTimes(ctx); err != nil {
-		return fmt.Errorf("issue.loadAttributes: loadTotalTrackedTimes: %v", err)
+		return fmt.Errorf("issue.loadAttributes: loadTotalTrackedTimes: %w", err)
 	}
 
 	return nil
diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go
index 6b978f9ae6020..f2cfca9bc0afc 100644
--- a/models/issues/issue_list_test.go
+++ b/models/issues/issue_list_test.go
@@ -18,9 +18,9 @@ func TestIssueList_LoadRepositories(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	issueList := issues_model.IssueList{
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue),
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}).(*issues_model.Issue),
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}).(*issues_model.Issue),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
 	}
 
 	repos, err := issueList.LoadRepositories()
@@ -35,8 +35,8 @@ func TestIssueList_LoadAttributes(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	setting.Service.EnableTimetracking = true
 	issueList := issues_model.IssueList{
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue),
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}).(*issues_model.Issue),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}),
 	}
 
 	assert.NoError(t, issueList.LoadAttributes())
diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go
index 5e0a337f7d63b..8299087c5b108 100644
--- a/models/issues/issue_project.go
+++ b/models/issues/issue_project.go
@@ -14,32 +14,32 @@ import (
 )
 
 // LoadProject load the project the issue was assigned to
-func (i *Issue) LoadProject() (err error) {
-	return i.loadProject(db.DefaultContext)
+func (issue *Issue) LoadProject() (err error) {
+	return issue.loadProject(db.DefaultContext)
 }
 
-func (i *Issue) loadProject(ctx context.Context) (err error) {
-	if i.Project == nil {
+func (issue *Issue) loadProject(ctx context.Context) (err error) {
+	if issue.Project == nil {
 		var p project_model.Project
 		if _, err = db.GetEngine(ctx).Table("project").
 			Join("INNER", "project_issue", "project.id=project_issue.project_id").
-			Where("project_issue.issue_id = ?", i.ID).
+			Where("project_issue.issue_id = ?", issue.ID).
 			Get(&p); err != nil {
 			return err
 		}
-		i.Project = &p
+		issue.Project = &p
 	}
-	return
+	return err
 }
 
 // ProjectID return project id if issue was assigned to one
-func (i *Issue) ProjectID() int64 {
-	return i.projectID(db.DefaultContext)
+func (issue *Issue) ProjectID() int64 {
+	return issue.projectID(db.DefaultContext)
 }
 
-func (i *Issue) projectID(ctx context.Context) int64 {
+func (issue *Issue) projectID(ctx context.Context) int64 {
 	var ip project_model.ProjectIssue
-	has, err := db.GetEngine(ctx).Where("issue_id=?", i.ID).Get(&ip)
+	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 	if err != nil || !has {
 		return 0
 	}
@@ -47,13 +47,13 @@ func (i *Issue) projectID(ctx context.Context) int64 {
 }
 
 // ProjectBoardID return project board id if issue was assigned to one
-func (i *Issue) ProjectBoardID() int64 {
-	return i.projectBoardID(db.DefaultContext)
+func (issue *Issue) ProjectBoardID() int64 {
+	return issue.projectBoardID(db.DefaultContext)
 }
 
-func (i *Issue) projectBoardID(ctx context.Context) int64 {
+func (issue *Issue) projectBoardID(ctx context.Context) int64 {
 	var ip project_model.ProjectIssue
-	has, err := db.GetEngine(ctx).Where("issue_id=?", i.ID).Get(&ip)
+	has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
 	if err != nil || !has {
 		return 0
 	}
@@ -68,6 +68,7 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
 		issues, err := Issues(&IssuesOptions{
 			ProjectBoardID: b.ID,
 			ProjectID:      b.ProjectID,
+			SortType:       "project-column-sorting",
 		})
 		if err != nil {
 			return nil, err
@@ -79,6 +80,7 @@ func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
 		issues, err := Issues(&IssuesOptions{
 			ProjectBoardID: -1, // Issues without ProjectBoardID
 			ProjectID:      b.ProjectID,
+			SortType:       "project-column-sorting",
 		})
 		if err != nil {
 			return nil, err
@@ -124,6 +126,17 @@ func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64
 func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
 	oldProjectID := issue.projectID(ctx)
 
+	// Only check if we add a new project and not remove it.
+	if newProjectID > 0 {
+		newProject, err := project_model.GetProjectByID(ctx, newProjectID)
+		if err != nil {
+			return err
+		}
+		if newProject.RepoID != issue.RepoID {
+			return fmt.Errorf("issue's repository is not the same as project's repository")
+		}
+	}
+
 	if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
 		return err
 	}
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 019e578da874d..bef5d03e8afa8 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -29,13 +29,13 @@ func TestIssue_ReplaceLabels(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	testSuccess := func(issueID int64, labelIDs []int64) {
-		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID}).(*issues_model.Issue)
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository)
-		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
+		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID})
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
+		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 
 		labels := make([]*issues_model.Label, len(labelIDs))
 		for i, labelID := range labelIDs {
-			labels[i] = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID, RepoID: repo.ID}).(*issues_model.Label)
+			labels[i] = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID, RepoID: repo.ID})
 		}
 		assert.NoError(t, issues_model.ReplaceIssueLabels(issue, labels, doer))
 		unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issueID}, len(labelIDs))
@@ -59,7 +59,7 @@ func Test_GetIssueIDsByRepoID(t *testing.T) {
 
 func TestIssueAPIURL(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 	err := issue.LoadAttributes(db.DefaultContext)
 
 	assert.NoError(t, err)
@@ -117,8 +117,8 @@ func TestIssue_ClearLabels(t *testing.T) {
 	}
 	for _, test := range tests {
 		assert.NoError(t, unittest.PrepareTestDatabase())
-		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}).(*issues_model.Issue)
-		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID}).(*user_model.User)
+		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID})
+		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID})
 		assert.NoError(t, issues_model.ClearIssueLabels(issue, doer))
 		unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID})
 	}
@@ -126,7 +126,7 @@ func TestIssue_ClearLabels(t *testing.T) {
 
 func TestUpdateIssueCols(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{})
 
 	const newTitle = "New Title for unit test"
 	issue.Title = newTitle
@@ -138,7 +138,7 @@ func TestUpdateIssueCols(t *testing.T) {
 	assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "name"))
 	then := time.Now().Unix()
 
-	updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}).(*issues_model.Issue)
+	updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID})
 	assert.EqualValues(t, newTitle, updatedIssue.Title)
 	assert.EqualValues(t, prevContent, updatedIssue.Content)
 	unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix))
@@ -291,8 +291,8 @@ func TestGetUserIssueStats(t *testing.T) {
 		{
 			issues_model.UserIssueStatsOptions{
 				UserID:     2,
-				Org:        unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization),
-				Team:       unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}).(*organization.Team),
+				Org:        unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}),
+				Team:       unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}),
 				FilterMode: issues_model.FilterModeAll,
 			},
 			issues_model.IssueStats{
@@ -347,7 +347,7 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) {
 
 func TestGetRepoIDsForIssuesOptions(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	for _, test := range []struct {
 		Opts            issues_model.IssuesOptions
 		ExpectedRepoIDs []int64
@@ -378,8 +378,8 @@ func TestGetRepoIDsForIssuesOptions(t *testing.T) {
 func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue {
 	var newIssue issues_model.Issue
 	t.Run(title, func(t *testing.T) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 		issue := issues_model.Issue{
 			RepoID:   repo.ID,
@@ -420,10 +420,10 @@ func TestIssue_ResolveMentions(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) {
-		o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner}).(*user_model.User)
-		r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo}).(*repo_model.Repository)
+		o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner})
+		r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo})
 		issue := &issues_model.Issue{RepoID: r.ID}
-		d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer}).(*user_model.User)
+		d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer})
 		resolved, err := issues_model.ResolveIssueMentionsByVisibility(db.DefaultContext, issue, d, mentions)
 		assert.NoError(t, err)
 		ids := make([]int64, len(resolved))
@@ -504,7 +504,7 @@ func TestCorrectIssueStats(t *testing.T) {
 
 func TestIssueForeignReference(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4})
 	assert.NotEqualValues(t, issue.Index, issue.ID) // make sure they are different to avoid false positive
 
 	// it is fine for an issue to not have a foreign reference
@@ -537,7 +537,7 @@ func TestIssueForeignReference(t *testing.T) {
 func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	miles := issues_model.MilestoneList{
-		unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}),
 	}
 
 	assert.NoError(t, miles.LoadTotalTrackedTimes())
@@ -547,7 +547,7 @@ func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
 
 func TestLoadTotalTrackedTime(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
+	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
 
 	assert.NoError(t, milestone.LoadTotalTrackedTime())
 
diff --git a/models/issues/issue_user.go b/models/issues/issue_user.go
index f5d22589af337..c1a68c96e817c 100644
--- a/models/issues/issue_user.go
+++ b/models/issues/issue_user.go
@@ -29,7 +29,7 @@ func init() {
 func NewIssueUsers(ctx context.Context, repo *repo_model.Repository, issue *Issue) error {
 	assignees, err := repo_model.GetRepoAssignees(ctx, repo)
 	if err != nil {
-		return fmt.Errorf("getAssignees: %v", err)
+		return fmt.Errorf("getAssignees: %w", err)
 	}
 
 	// Poster can be anyone, append later if not one of assignees.
diff --git a/models/issues/issue_user_test.go b/models/issues/issue_user_test.go
index 33e9f98ecc43d..7dd84ed68cdf0 100644
--- a/models/issues/issue_user_test.go
+++ b/models/issues/issue_user_test.go
@@ -18,7 +18,7 @@ import (
 func Test_NewIssueUsers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	newIssue := &issues_model.Issue{
 		RepoID:   repo.ID,
 		PosterID: 4,
@@ -39,7 +39,7 @@ func Test_NewIssueUsers(t *testing.T) {
 
 func TestUpdateIssueUserByRead(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 
 	assert.NoError(t, issues_model.UpdateIssueUserByRead(4, issue.ID))
 	unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1")
@@ -52,7 +52,7 @@ func TestUpdateIssueUserByRead(t *testing.T) {
 
 func TestUpdateIssueUsersByMentions(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 
 	uids := []int64{2, 5}
 	assert.NoError(t, issues_model.UpdateIssueUsersByMentions(db.DefaultContext, issue.ID, uids))
diff --git a/models/issues/issue_watch.go b/models/issues/issue_watch.go
index bf907aa8fd79c..cb9d7e7125e9d 100644
--- a/models/issues/issue_watch.go
+++ b/models/issues/issue_watch.go
@@ -65,7 +65,7 @@ func GetIssueWatch(ctx context.Context, userID, issueID int64) (iw *IssueWatch,
 		Where("user_id = ?", userID).
 		And("issue_id = ?", issueID).
 		Get(iw)
-	return
+	return iw, exists, err
 }
 
 // CheckIssueWatch check if an user is watching an issue
diff --git a/models/issues/issue_watch_test.go b/models/issues/issue_watch_test.go
index c6b6416d9b321..7aaf9f7f5da5a 100644
--- a/models/issues/issue_watch_test.go
+++ b/models/issues/issue_watch_test.go
@@ -18,11 +18,11 @@ func TestCreateOrUpdateIssueWatch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(3, 1, true))
-	iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1}).(*issues_model.IssueWatch)
+	iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1})
 	assert.True(t, iw.IsWatching)
 
 	assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(1, 1, false))
-	iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1}).(*issues_model.IssueWatch)
+	iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1})
 	assert.False(t, iw.IsWatching)
 }
 
diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go
index f4380a02ec724..e389f63d72acd 100644
--- a/models/issues/issue_xref.go
+++ b/models/issues/issue_xref.go
@@ -231,46 +231,46 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
 }
 
 // AddCrossReferences add cross references
-func (comment *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
-	if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment {
+func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
+	if c.Type != CommentTypeCode && c.Type != CommentTypeComment {
 		return nil
 	}
-	if err := comment.LoadIssueCtx(stdCtx); err != nil {
+	if err := c.LoadIssueCtx(stdCtx); err != nil {
 		return err
 	}
 	ctx := &crossReferencesContext{
 		Type:        CommentTypeCommentRef,
 		Doer:        doer,
-		OrigIssue:   comment.Issue,
-		OrigComment: comment,
+		OrigIssue:   c.Issue,
+		OrigComment: c,
 		RemoveOld:   removeOld,
 	}
-	return comment.Issue.createCrossReferences(stdCtx, ctx, "", comment.Content)
+	return c.Issue.createCrossReferences(stdCtx, ctx, "", c.Content)
 }
 
-func (comment *Comment) neuterCrossReferences(ctx context.Context) error {
-	return neuterCrossReferences(ctx, comment.IssueID, comment.ID)
+func (c *Comment) neuterCrossReferences(ctx context.Context) error {
+	return neuterCrossReferences(ctx, c.IssueID, c.ID)
 }
 
 // LoadRefComment loads comment that created this reference from database
-func (comment *Comment) LoadRefComment() (err error) {
-	if comment.RefComment != nil {
+func (c *Comment) LoadRefComment() (err error) {
+	if c.RefComment != nil {
 		return nil
 	}
-	comment.RefComment, err = GetCommentByID(db.DefaultContext, comment.RefCommentID)
-	return
+	c.RefComment, err = GetCommentByID(db.DefaultContext, c.RefCommentID)
+	return err
 }
 
 // LoadRefIssue loads comment that created this reference from database
-func (comment *Comment) LoadRefIssue() (err error) {
-	if comment.RefIssue != nil {
+func (c *Comment) LoadRefIssue() (err error) {
+	if c.RefIssue != nil {
 		return nil
 	}
-	comment.RefIssue, err = GetIssueByID(db.DefaultContext, comment.RefIssueID)
+	c.RefIssue, err = GetIssueByID(db.DefaultContext, c.RefIssueID)
 	if err == nil {
-		err = comment.RefIssue.LoadRepo(db.DefaultContext)
+		err = c.RefIssue.LoadRepo(db.DefaultContext)
 	}
-	return
+	return err
 }
 
 // CommentTypeIsRef returns true if CommentType is a reference from another issue
@@ -279,44 +279,44 @@ func CommentTypeIsRef(t CommentType) bool {
 }
 
 // RefCommentHTMLURL returns the HTML URL for the comment that created this reference
-func (comment *Comment) RefCommentHTMLURL() string {
+func (c *Comment) RefCommentHTMLURL() string {
 	// Edge case for when the reference is inside the title or the description of the referring issue
-	if comment.RefCommentID == 0 {
-		return comment.RefIssueHTMLURL()
+	if c.RefCommentID == 0 {
+		return c.RefIssueHTMLURL()
 	}
-	if err := comment.LoadRefComment(); err != nil { // Silently dropping errors :unamused:
-		log.Error("LoadRefComment(%d): %v", comment.RefCommentID, err)
+	if err := c.LoadRefComment(); err != nil { // Silently dropping errors :unamused:
+		log.Error("LoadRefComment(%d): %v", c.RefCommentID, err)
 		return ""
 	}
-	return comment.RefComment.HTMLURL()
+	return c.RefComment.HTMLURL()
 }
 
 // RefIssueHTMLURL returns the HTML URL of the issue where this reference was created
-func (comment *Comment) RefIssueHTMLURL() string {
-	if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
-		log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
+func (c *Comment) RefIssueHTMLURL() string {
+	if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
+		log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err)
 		return ""
 	}
-	return comment.RefIssue.HTMLURL()
+	return c.RefIssue.HTMLURL()
 }
 
 // RefIssueTitle returns the title of the issue where this reference was created
-func (comment *Comment) RefIssueTitle() string {
-	if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
-		log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
+func (c *Comment) RefIssueTitle() string {
+	if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
+		log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err)
 		return ""
 	}
-	return comment.RefIssue.Title
+	return c.RefIssue.Title
 }
 
 // RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created
-func (comment *Comment) RefIssueIdent() string {
-	if err := comment.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
-		log.Error("LoadRefIssue(%d): %v", comment.RefCommentID, err)
+func (c *Comment) RefIssueIdent() string {
+	if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused:
+		log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err)
 		return ""
 	}
 	// FIXME: check this name for cross-repository references (#7901 if it gets merged)
-	return fmt.Sprintf("#%d", comment.RefIssue.Index)
+	return fmt.Sprintf("#%d", c.RefIssue.Index)
 }
 
 // __________      .__  .__ __________                                     __
@@ -334,7 +334,7 @@ func (pr *PullRequest) ResolveCrossReferences(ctx context.Context) ([]*Comment,
 		In("ref_action", []references.XRefAction{references.XRefActionCloses, references.XRefActionReopens}).
 		OrderBy("id").
 		Find(&unfiltered); err != nil {
-		return nil, fmt.Errorf("get reference: %v", err)
+		return nil, fmt.Errorf("get reference: %w", err)
 	}
 
 	refs := make([]*Comment, 0, len(unfiltered))
diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go
index 6bb19d5328d5c..0f72fc7ca6f52 100644
--- a/models/issues/issue_xref_test.go
+++ b/models/issues/issue_xref_test.go
@@ -27,7 +27,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
 	// PR to close issue #1
 	content := fmt.Sprintf("content2, closes #%d", itarget.Index)
 	pr := testCreateIssue(t, 1, 2, "title2", content, true)
-	ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: 0}).(*issues_model.Comment)
+	ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: 0})
 	assert.Equal(t, issues_model.CommentTypePullRef, ref.Type)
 	assert.Equal(t, pr.RepoID, ref.RefRepoID)
 	assert.True(t, ref.RefIsPull)
@@ -36,7 +36,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
 	// Comment on PR to reopen issue #1
 	content = fmt.Sprintf("content2, reopens #%d", itarget.Index)
 	c := testCreateComment(t, 1, 2, pr.ID, content)
-	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID}).(*issues_model.Comment)
+	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID})
 	assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type)
 	assert.Equal(t, pr.RepoID, ref.RefRepoID)
 	assert.True(t, ref.RefIsPull)
@@ -45,7 +45,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
 	// Issue mentioning issue #1
 	content = fmt.Sprintf("content3, mentions #%d", itarget.Index)
 	i := testCreateIssue(t, 1, 2, "title3", content, false)
-	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*issues_model.Comment)
+	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
 	assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
 	assert.Equal(t, pr.RepoID, ref.RefRepoID)
 	assert.False(t, ref.RefIsPull)
@@ -57,7 +57,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
 	// Cross-reference to issue #4 by admin
 	content = fmt.Sprintf("content5, mentions user3/repo3#%d", itarget.Index)
 	i = testCreateIssue(t, 2, 1, "title5", content, false)
-	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*issues_model.Comment)
+	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
 	assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
 	assert.Equal(t, i.RepoID, ref.RefRepoID)
 	assert.False(t, ref.RefIsPull)
@@ -78,15 +78,15 @@ func TestXRef_NeuterCrossReferences(t *testing.T) {
 	// Issue mentioning issue #1
 	title := fmt.Sprintf("title2, mentions #%d", itarget.Index)
 	i := testCreateIssue(t, 1, 2, title, "content2", false)
-	ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*issues_model.Comment)
+	ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
 	assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
 	assert.Equal(t, references.XRefActionNone, ref.RefAction)
 
-	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	i.Title = "title2, no mentions"
 	assert.NoError(t, issues_model.ChangeIssueTitle(i, d, title))
 
-	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0}).(*issues_model.Comment)
+	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: i.ID, RefCommentID: 0})
 	assert.Equal(t, issues_model.CommentTypeIssueRef, ref.Type)
 	assert.Equal(t, references.XRefActionNeutered, ref.RefAction)
 }
@@ -94,7 +94,7 @@ func TestXRef_NeuterCrossReferences(t *testing.T) {
 func TestXRef_ResolveCrossReferences(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
 	i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
@@ -103,10 +103,10 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
 	assert.NoError(t, err)
 
 	pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
-	rp := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i1.ID, RefIssueID: pr.Issue.ID, RefCommentID: 0}).(*issues_model.Comment)
+	rp := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i1.ID, RefIssueID: pr.Issue.ID, RefCommentID: 0})
 
 	c1 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i2.Index))
-	r1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID}).(*issues_model.Comment)
+	r1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID})
 
 	// Must be ignored
 	c2 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("mentions #%d", i2.Index))
@@ -117,7 +117,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
 	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c3.ID})
 
 	c4 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i3.Index))
-	r4 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID}).(*issues_model.Comment)
+	r4 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID})
 
 	refs, err := pr.ResolveCrossReferences(db.DefaultContext)
 	assert.NoError(t, err)
@@ -128,10 +128,14 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
 }
 
 func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispull bool) *issues_model.Issue {
-	r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo}).(*repo_model.Repository)
-	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer}).(*user_model.User)
+	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,
@@ -159,8 +160,8 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu
 }
 
 func testCreatePR(t *testing.T, repo, doer int64, title, content string) *issues_model.PullRequest {
-	r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo}).(*repo_model.Repository)
-	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer}).(*user_model.User)
+	r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo})
+	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
 	i := &issues_model.Issue{RepoID: r.ID, PosterID: d.ID, Poster: d, Title: title, Content: content, IsPull: true}
 	pr := &issues_model.PullRequest{HeadRepoID: repo, BaseRepoID: repo, HeadBranch: "head", BaseBranch: "base", Status: issues_model.PullRequestStatusMergeable}
 	assert.NoError(t, issues_model.NewPullRequest(db.DefaultContext, r, i, nil, nil, pr))
@@ -169,8 +170,8 @@ func testCreatePR(t *testing.T, repo, doer int64, title, content string) *issues
 }
 
 func testCreateComment(t *testing.T, repo, doer, issue int64, content string) *issues_model.Comment {
-	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer}).(*user_model.User)
-	i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue}).(*issues_model.Issue)
+	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
+	i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue})
 	c := &issues_model.Comment{Type: issues_model.CommentTypeComment, PosterID: doer, Poster: d, IssueID: issue, Issue: i, Content: content}
 
 	ctx, committer, err := db.TxContext()
diff --git a/models/issues/label.go b/models/issues/label.go
index 9ad488252ab7f..bbdc99e265101 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -17,6 +17,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"
 
 	"xorm.io/builder"
 )
@@ -37,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
@@ -53,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
@@ -68,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})$")
 
@@ -107,6 +120,7 @@ func (label *Label) CalOpenOrgIssues(repoID, labelID int64) {
 	counts, _ := CountIssuesByRepo(&IssuesOptions{
 		RepoID:   repoID,
 		LabelIDs: []int64{labelID},
+		IsClosed: util.OptionalBoolFalse,
 	})
 
 	for _, count := range counts {
@@ -653,7 +667,7 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
 		}
 
 		if err = newIssueLabel(ctx, issue, label, doer); err != nil {
-			return fmt.Errorf("newIssueLabel: %v", err)
+			return fmt.Errorf("newIssueLabel: %w", err)
 		}
 	}
 
diff --git a/models/issues/label_test.go b/models/issues/label_test.go
index 33f114b5fe098..9ad6fd427b923 100644
--- a/models/issues/label_test.go
+++ b/models/issues/label_test.go
@@ -21,17 +21,17 @@ import (
 
 func TestLabel_CalOpenIssues(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	label.CalOpenIssues()
 	assert.EqualValues(t, 2, label.NumOpenIssues)
 }
 
 func TestLabel_ForegroundColor(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	assert.Equal(t, template.CSS("#000"), label.ForegroundColor())
 
-	label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}).(*issues_model.Label)
+	label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
 	assert.Equal(t, template.CSS("#fff"), label.ForegroundColor())
 }
 
@@ -260,7 +260,7 @@ func TestGetLabelsByIssueID(t *testing.T) {
 
 func TestUpdateLabel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	// make sure update wont overwrite it
 	update := &issues_model.Label{
 		ID:          label.ID,
@@ -271,7 +271,7 @@ func TestUpdateLabel(t *testing.T) {
 	label.Color = update.Color
 	label.Name = update.Name
 	assert.NoError(t, issues_model.UpdateLabel(update))
-	newLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	newLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	assert.EqualValues(t, label.ID, newLabel.ID)
 	assert.EqualValues(t, label.Color, newLabel.Color)
 	assert.EqualValues(t, label.Name, newLabel.Name)
@@ -281,7 +281,7 @@ func TestUpdateLabel(t *testing.T) {
 
 func TestDeleteLabel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	assert.NoError(t, issues_model.DeleteLabel(label.RepoID, label.ID))
 	unittest.AssertNotExistsBean(t, &issues_model.Label{ID: label.ID, RepoID: label.RepoID})
 
@@ -301,9 +301,9 @@ func TestHasIssueLabel(t *testing.T) {
 
 func TestNewIssueLabel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}).(*issues_model.Label)
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
-	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	// add new IssueLabel
 	prevNumIssues := label.NumIssues
@@ -316,7 +316,7 @@ func TestNewIssueLabel(t *testing.T) {
 		LabelID:  label.ID,
 		Content:  "1",
 	})
-	label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}).(*issues_model.Label)
+	label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
 	assert.EqualValues(t, prevNumIssues+1, label.NumIssues)
 
 	// re-add existing IssueLabel
@@ -326,10 +326,10 @@ func TestNewIssueLabel(t *testing.T) {
 
 func TestNewIssueLabels(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
-	label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}).(*issues_model.Label)
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 5}).(*issues_model.Issue)
-	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
+	label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 5})
+	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	assert.NoError(t, issues_model.NewIssueLabels(issue, []*issues_model.Label{label1, label2}, doer))
 	unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label1.ID})
@@ -341,10 +341,10 @@ func TestNewIssueLabels(t *testing.T) {
 		Content:  "1",
 	})
 	unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label1.ID})
-	label1 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
+	label1 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	assert.EqualValues(t, 3, label1.NumIssues)
 	assert.EqualValues(t, 1, label1.NumClosedIssues)
-	label2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}).(*issues_model.Label)
+	label2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
 	assert.EqualValues(t, 1, label2.NumIssues)
 	assert.EqualValues(t, 1, label2.NumClosedIssues)
 
@@ -357,9 +357,9 @@ func TestNewIssueLabels(t *testing.T) {
 func TestDeleteIssueLabel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	testSuccess := func(labelID, issueID, doerID int64) {
-		label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID}).(*issues_model.Label)
-		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID}).(*issues_model.Issue)
-		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doerID}).(*user_model.User)
+		label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID})
+		issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID})
+		doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doerID})
 
 		expectedNumIssues := label.NumIssues
 		expectedNumClosedIssues := label.NumClosedIssues
@@ -383,7 +383,7 @@ func TestDeleteIssueLabel(t *testing.T) {
 			IssueID:  issueID,
 			LabelID:  labelID,
 		}, `content=""`)
-		label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID}).(*issues_model.Label)
+		label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID})
 		assert.EqualValues(t, expectedNumIssues, label.NumIssues)
 		assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues)
 	}
diff --git a/models/issues/milestone.go b/models/issues/milestone.go
index 6c1095910877f..3ccade7411e5f 100644
--- a/models/issues/milestone.go
+++ b/models/issues/milestone.go
@@ -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"`
@@ -124,6 +129,11 @@ func NewMilestone(m *Milestone) (err error) {
 	return committer.Commit()
 }
 
+// HasMilestoneByRepoID returns if the milestone exists in the repository.
+func HasMilestoneByRepoID(ctx context.Context, repoID, id int64) (bool, error) {
+	return db.GetEngine(ctx).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone))
+}
+
 // GetMilestoneByRepoID returns the milestone in a repository.
 func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) {
 	m := new(Milestone)
@@ -356,7 +366,7 @@ func (opts GetMilestonesOption) toCond() builder.Cond {
 	}
 
 	if len(opts.Name) != 0 {
-		cond = cond.And(builder.Like{"name", opts.Name})
+		cond = cond.And(db.BuildCaseInsensitiveLike("name", opts.Name))
 	}
 
 	return cond
diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go
index a6fbf9c23b531..f04a2b2b3bef5 100644
--- a/models/issues/milestone_test.go
+++ b/models/issues/milestone_test.go
@@ -40,7 +40,7 @@ func TestGetMilestoneByRepoID(t *testing.T) {
 func TestGetMilestonesByRepoID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	test := func(repoID int64, state api.StateType) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
 			RepoID: repo.ID,
 			State:  state,
@@ -88,7 +88,7 @@ func TestGetMilestonesByRepoID(t *testing.T) {
 
 func TestGetMilestones(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
 		for _, page := range []int{0, 1} {
 			milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
@@ -150,7 +150,7 @@ func TestGetMilestones(t *testing.T) {
 func TestCountRepoMilestones(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	test := func(repoID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
 			RepoID: repoID,
 			State:  api.StateAll,
@@ -173,7 +173,7 @@ func TestCountRepoMilestones(t *testing.T) {
 func TestCountRepoClosedMilestones(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	test := func(repoID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		count, err := issues_model.CountMilestones(db.DefaultContext, issues_model.GetMilestonesOption{
 			RepoID: repoID,
 			State:  api.StateClosed,
@@ -196,7 +196,7 @@ func TestCountRepoClosedMilestones(t *testing.T) {
 func TestCountMilestonesByRepoIDs(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	milestonesCount := func(repoID int64) (int, int) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		return repo.NumOpenMilestones, repo.NumClosedMilestones
 	}
 	repo1OpenCount, repo1ClosedCount := milestonesCount(1)
@@ -215,8 +215,8 @@ func TestCountMilestonesByRepoIDs(t *testing.T) {
 
 func TestGetMilestonesByRepoIDs(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 	test := func(sortType string, sortCond func(*issues_model.Milestone) int) {
 		for _, page := range []int{0, 1} {
 			openMilestones, err := issues_model.GetMilestonesByRepoIDs([]int64{repo1.ID, repo2.ID}, page, false, sortType)
@@ -262,7 +262,7 @@ func TestGetMilestonesStats(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(repoID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		stats, err := issues_model.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"repo_id": repoID}))
 		assert.NoError(t, err)
 		assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, stats.OpenCount)
@@ -277,8 +277,8 @@ func TestGetMilestonesStats(t *testing.T) {
 	assert.EqualValues(t, 0, stats.OpenCount)
 	assert.EqualValues(t, 0, stats.ClosedCount)
 
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 
 	milestoneStats, err := issues_model.GetMilestonesStatsByRepoCond(builder.In("repo_id", []int64{repo1.ID, repo2.ID}))
 	assert.NoError(t, err)
@@ -301,7 +301,7 @@ func TestNewMilestone(t *testing.T) {
 
 func TestChangeMilestoneStatus(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
+	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
 
 	assert.NoError(t, issues_model.ChangeMilestoneStatus(milestone, true))
 	unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=1")
@@ -324,11 +324,11 @@ func TestDeleteMilestoneByRepoID(t *testing.T) {
 func TestUpdateMilestone(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
+	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
 	milestone.Name = " newMilestoneName  "
 	milestone.Content = "newMilestoneContent"
 	assert.NoError(t, issues_model.UpdateMilestone(milestone, milestone.IsClosed))
-	milestone = unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
+	milestone = unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
 	assert.EqualValues(t, "newMilestoneName", milestone.Name)
 	unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
 }
@@ -336,7 +336,7 @@ func TestUpdateMilestone(t *testing.T) {
 func TestUpdateMilestoneCounters(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{MilestoneID: 1},
-		"is_closed=0").(*issues_model.Issue)
+		"is_closed=0")
 
 	issue.IsClosed = true
 	issue.ClosedUnix = timeutil.TimeStampNow()
diff --git a/models/issues/pull.go b/models/issues/pull.go
index f2ca19b03e9a5..f03cabc3c828d 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -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
@@ -122,6 +130,7 @@ const (
 	PullRequestStatusManuallyMerged
 	PullRequestStatusError
 	PullRequestStatusEmpty
+	PullRequestStatusAncestor
 )
 
 // PullRequestFlow the flow of pull request
@@ -219,7 +228,7 @@ func (pr *PullRequest) loadAttributes(ctx context.Context) (err error) {
 			pr.MergerID = -1
 			pr.Merger = user_model.NewGhostUser()
 		} else if err != nil {
-			return fmt.Errorf("getUserByID [%d]: %v", pr.MergerID, err)
+			return fmt.Errorf("getUserByID [%d]: %w", pr.MergerID, err)
 		}
 	}
 
@@ -246,7 +255,7 @@ func (pr *PullRequest) LoadHeadRepoCtx(ctx context.Context) (err error) {
 
 		pr.HeadRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.HeadRepoID)
 		if err != nil && !repo_model.IsErrRepoNotExist(err) { // Head repo maybe deleted, but it should still work
-			return fmt.Errorf("getRepositoryByID(head): %v", err)
+			return fmt.Errorf("getRepositoryByID(head): %w", err)
 		}
 		pr.isHeadRepoLoaded = true
 	}
@@ -281,7 +290,7 @@ func (pr *PullRequest) LoadBaseRepoCtx(ctx context.Context) (err error) {
 
 	pr.BaseRepo, err = repo_model.GetRepositoryByIDCtx(ctx, pr.BaseRepoID)
 	if err != nil {
-		return fmt.Errorf("repo_model.GetRepositoryByID(base): %v", err)
+		return fmt.Errorf("repo_model.GetRepositoryByID(base): %w", err)
 	}
 	return nil
 }
@@ -323,7 +332,7 @@ func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) {
 		}
 		pr.ProtectedBranch, err = git_model.GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch)
 	}
-	return
+	return err
 }
 
 // ReviewCount represents a count of Reviews
@@ -423,6 +432,11 @@ func (pr *PullRequest) IsEmpty() bool {
 	return pr.Status == PullRequestStatusEmpty
 }
 
+// IsAncestor returns true if the Head Commit of this PR is an ancestor of the Base Commit
+func (pr *PullRequest) IsAncestor() bool {
+	return pr.Status == PullRequestStatusAncestor
+}
+
 // SetMerged sets a pull request to merged and closes the corresponding issue
 func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
 	if pr.HasMerged {
@@ -468,7 +482,7 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
 	}
 
 	if _, err := changeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
-		return false, fmt.Errorf("Issue.changeStatus: %v", err)
+		return false, fmt.Errorf("Issue.changeStatus: %w", err)
 	}
 
 	// reset the conflicted files as there cannot be any if we're merged
@@ -476,7 +490,7 @@ func (pr *PullRequest) SetMerged(ctx context.Context) (bool, error) {
 
 	// We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
 	if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").Update(pr); err != nil {
-		return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
+		return false, fmt.Errorf("Failed to update pr[%d]: %w", pr.ID, err)
 	}
 
 	return true, nil
@@ -484,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
@@ -498,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: %w", err)
+	}
+
+	issue.Index = idx
+
 	if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
 		Repo:        repo,
 		Issue:       issue,
@@ -508,18 +522,18 @@ func NewPullRequest(outerCtx context.Context, repo *repo_model.Repository, issue
 		if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
 			return err
 		}
-		return fmt.Errorf("newIssue: %v", err)
+		return fmt.Errorf("newIssue: %w", err)
 	}
 
 	pr.Index = issue.Index
 	pr.BaseRepo = repo
 	pr.IssueID = issue.ID
 	if err = db.Insert(ctx, pr); err != nil {
-		return fmt.Errorf("insert pull repo: %v", err)
+		return fmt.Errorf("insert pull repo: %w", err)
 	}
 
 	if err = committer.Commit(); err != nil {
-		return fmt.Errorf("Commit: %v", err)
+		return fmt.Errorf("Commit: %w", err)
 	}
 
 	return nil
diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go
index 9ca536909eb73..c69f18492b4cf 100644
--- a/models/issues/pull_list.go
+++ b/models/issues/pull_list.go
@@ -62,7 +62,7 @@ func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequ
 		Find(&prs)
 }
 
-// CanMaintainerWriteToBranch check whether user is a matainer and could write to the branch
+// CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch
 func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *user_model.User) bool {
 	if p.CanWrite(unit.TypeCode) {
 		return true
@@ -168,7 +168,7 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
 		Where("id > 0").
 		In("id", issueIDs).
 		Find(&issues); err != nil {
-		return fmt.Errorf("find issues: %v", err)
+		return fmt.Errorf("find issues: %w", err)
 	}
 
 	set := make(map[int64]*Issue)
@@ -205,7 +205,7 @@ func (prs PullRequestList) InvalidateCodeComments(ctx context.Context, doer *use
 		Where("type = ? and invalidated = ?", CommentTypeCode, false).
 		In("issue_id", issueIDs).
 		Find(&codeComments); err != nil {
-		return fmt.Errorf("find code comments: %v", err)
+		return fmt.Errorf("find code comments: %w", err)
 	}
 	for _, comment := range codeComments {
 		if err := comment.CheckInvalidation(repo, doer, branch); err != nil {
diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go
index 0d1991383d310..fb46e3071e39f 100644
--- a/models/issues/pull_test.go
+++ b/models/issues/pull_test.go
@@ -16,7 +16,7 @@ import (
 
 func TestPullRequest_LoadAttributes(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadAttributes())
 	assert.NotNil(t, pr.Merger)
 	assert.Equal(t, pr.MergerID, pr.Merger.ID)
@@ -24,7 +24,7 @@ func TestPullRequest_LoadAttributes(t *testing.T) {
 
 func TestPullRequest_LoadIssue(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadIssue())
 	assert.NotNil(t, pr.Issue)
 	assert.Equal(t, int64(2), pr.Issue.ID)
@@ -35,7 +35,7 @@ func TestPullRequest_LoadIssue(t *testing.T) {
 
 func TestPullRequest_LoadBaseRepo(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadBaseRepo())
 	assert.NotNil(t, pr.BaseRepo)
 	assert.Equal(t, pr.BaseRepoID, pr.BaseRepo.ID)
@@ -46,7 +46,7 @@ func TestPullRequest_LoadBaseRepo(t *testing.T) {
 
 func TestPullRequest_LoadHeadRepo(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadHeadRepo())
 	assert.NotNil(t, pr.HeadRepo)
 	assert.Equal(t, pr.HeadRepoID, pr.HeadRepo.ID)
@@ -180,12 +180,12 @@ func TestGetPullRequestByIssueID(t *testing.T) {
 
 func TestPullRequest_Update(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	pr.BaseBranch = "baseBranch"
 	pr.HeadBranch = "headBranch"
 	pr.Update()
 
-	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}).(*issues_model.PullRequest)
+	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
 	assert.Equal(t, "baseBranch", pr.BaseBranch)
 	assert.Equal(t, "headBranch", pr.HeadBranch)
 	unittest.CheckConsistencyFor(t, pr)
@@ -200,7 +200,7 @@ func TestPullRequest_UpdateCols(t *testing.T) {
 	}
 	assert.NoError(t, pr.UpdateCols("head_branch"))
 
-	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.Equal(t, "master", pr.BaseBranch)
 	assert.Equal(t, "headBranch", pr.HeadBranch)
 	unittest.CheckConsistencyFor(t, pr)
@@ -210,8 +210,8 @@ func TestPullRequestList_LoadAttributes(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	prs := []*issues_model.PullRequest{
-		unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest),
-		unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}).(*issues_model.PullRequest),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}),
+		unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}),
 	}
 	assert.NoError(t, issues_model.PullRequestList(prs).LoadAttributes())
 	for _, pr := range prs {
@@ -227,7 +227,7 @@ func TestPullRequestList_LoadAttributes(t *testing.T) {
 func TestPullRequest_IsWorkInProgress(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
 	pr.LoadIssue()
 
 	assert.False(t, pr.IsWorkInProgress())
@@ -242,7 +242,7 @@ func TestPullRequest_IsWorkInProgress(t *testing.T) {
 func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}).(*issues_model.PullRequest)
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
 	pr.LoadIssue()
 
 	assert.Empty(t, pr.GetWorkInProgressPrefix())
diff --git a/models/issues/reaction.go b/models/issues/reaction.go
index 87d6ff431054a..c7503c23a2422 100644
--- a/models/issues/reaction.go
+++ b/models/issues/reaction.go
@@ -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"`
@@ -181,6 +190,10 @@ func createReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
 		Reaction:  opts.Type,
 		UserID:    opts.DoerID,
 	}
+	if findOpts.CommentID == 0 {
+		// explicit search of Issue Reactions where CommentID = 0
+		findOpts.CommentID = -1
+	}
 
 	existingR, _, err := FindReactions(ctx, findOpts)
 	if err != nil {
@@ -207,7 +220,7 @@ type ReactionOptions struct {
 
 // CreateReaction creates reaction for issue or comment.
 func CreateReaction(opts *ReactionOptions) (*Reaction, error) {
-	if !setting.UI.ReactionsMap[opts.Type] {
+	if !setting.UI.ReactionsLookup.Contains(opts.Type) {
 		return nil, ErrForbiddenIssueReaction{opts.Type}
 	}
 
@@ -256,16 +269,23 @@ func DeleteReaction(ctx context.Context, opts *ReactionOptions) error {
 		CommentID: opts.CommentID,
 	}
 
-	_, err := db.GetEngine(ctx).Where("original_author_id = 0").Delete(reaction)
+	sess := db.GetEngine(ctx).Where("original_author_id = 0")
+	if opts.CommentID == -1 {
+		reaction.CommentID = 0
+		sess.MustCols("comment_id")
+	}
+
+	_, err := sess.Delete(reaction)
 	return err
 }
 
 // DeleteIssueReaction deletes a reaction on issue.
 func DeleteIssueReaction(doerID, issueID int64, content string) error {
 	return DeleteReaction(db.DefaultContext, &ReactionOptions{
-		Type:    content,
-		DoerID:  doerID,
-		IssueID: issueID,
+		Type:      content,
+		DoerID:    doerID,
+		IssueID:   issueID,
+		CommentID: -1,
 	})
 }
 
@@ -305,16 +325,14 @@ func (list ReactionList) GroupByType() map[string]ReactionList {
 }
 
 func (list ReactionList) getUserIDs() []int64 {
-	userIDs := make(map[int64]struct{}, len(list))
+	userIDs := make(container.Set[int64], len(list))
 	for _, reaction := range list {
 		if reaction.OriginalAuthor != "" {
 			continue
 		}
-		if _, ok := userIDs[reaction.UserID]; !ok {
-			userIDs[reaction.UserID] = struct{}{}
-		}
+		userIDs.Add(reaction.UserID)
 	}
-	return container.KeysInt64(userIDs)
+	return userIDs.Values()
 }
 
 func valuesUser(m map[int64]*user_model.User) []*user_model.User {
@@ -337,7 +355,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
 		In("id", userIDs).
 		Find(&userMaps)
 	if err != nil {
-		return nil, fmt.Errorf("find user: %v", err)
+		return nil, fmt.Errorf("find user: %w", err)
 	}
 
 	for _, reaction := range list {
diff --git a/models/issues/reaction_test.go b/models/issues/reaction_test.go
index ee1b6687a2643..835a667619cba 100644
--- a/models/issues/reaction_test.go
+++ b/models/issues/reaction_test.go
@@ -32,7 +32,7 @@ func addReaction(t *testing.T, doerID, issueID, commentID int64, content string)
 func TestIssueAddReaction(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	var issue1ID int64 = 1
 
@@ -44,7 +44,7 @@ func TestIssueAddReaction(t *testing.T) {
 func TestIssueAddDuplicateReaction(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	var issue1ID int64 = 1
 
@@ -58,14 +58,14 @@ func TestIssueAddDuplicateReaction(t *testing.T) {
 	assert.Error(t, err)
 	assert.Equal(t, issues_model.ErrReactionAlreadyExist{Reaction: "heart"}, err)
 
-	existingR := unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}).(*issues_model.Reaction)
+	existingR := unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID})
 	assert.Equal(t, existingR.ID, reaction.ID)
 }
 
 func TestIssueDeleteReaction(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	var issue1ID int64 = 1
 
@@ -82,14 +82,14 @@ func TestIssueReactionCount(t *testing.T) {
 
 	setting.UI.ReactionMaxUserNum = 2
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
-	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
 	ghost := user_model.NewGhostUser()
 
 	var issueID int64 = 2
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	addReaction(t, user1.ID, issueID, 0, "heart")
 	addReaction(t, user2.ID, issueID, 0, "heart")
@@ -122,7 +122,7 @@ func TestIssueReactionCount(t *testing.T) {
 func TestIssueCommentAddReaction(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	var issue1ID int64 = 1
 	var comment1ID int64 = 1
@@ -135,10 +135,10 @@ func TestIssueCommentAddReaction(t *testing.T) {
 func TestIssueCommentDeleteReaction(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
-	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
 
 	var issue1ID int64 = 1
 	var comment1ID int64 = 1
@@ -163,7 +163,7 @@ func TestIssueCommentDeleteReaction(t *testing.T) {
 func TestIssueCommentReactionCount(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	var issue1ID int64 = 1
 	var comment1ID int64 = 1
diff --git a/models/issues/review.go b/models/issues/review.go
index ee65bec3f8825..3d2fceda2d949 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -38,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
@@ -58,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
 
@@ -134,7 +143,7 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
 		return
 	}
 	r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r)
-	return
+	return err
 }
 
 func (r *Review) loadIssue(ctx context.Context) (err error) {
@@ -142,7 +151,7 @@ func (r *Review) loadIssue(ctx context.Context) (err error) {
 		return
 	}
 	r.Issue, err = GetIssueByID(ctx, r.IssueID)
-	return
+	return err
 }
 
 func (r *Review) loadReviewer(ctx context.Context) (err error) {
@@ -150,7 +159,7 @@ func (r *Review) loadReviewer(ctx context.Context) (err error) {
 		return
 	}
 	r.Reviewer, err = user_model.GetUserByIDCtx(ctx, r.ReviewerID)
-	return
+	return err
 }
 
 func (r *Review) loadReviewerTeam(ctx context.Context) (err error) {
@@ -159,7 +168,7 @@ func (r *Review) loadReviewerTeam(ctx context.Context) (err error) {
 	}
 
 	r.ReviewerTeam, err = organization.GetTeamByID(ctx, r.ReviewerTeamID)
-	return
+	return err
 }
 
 // LoadReviewer loads reviewer
@@ -186,7 +195,7 @@ func (r *Review) LoadAttributes(ctx context.Context) (err error) {
 	if err = r.loadReviewerTeam(ctx); err != nil {
 		return
 	}
-	return
+	return err
 }
 
 // GetReviewByID returns the review by the given ID
@@ -474,6 +483,35 @@ func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, co
 	return review, comm, committer.Commit()
 }
 
+// GetReviewOptions represent filter options for GetReviews
+type GetReviewOptions struct {
+	IssueID    int64
+	ReviewerID int64
+	Dismissed  util.OptionalBool
+}
+
+// GetReviews return reviews based on GetReviewOptions
+func GetReviews(ctx context.Context, opts *GetReviewOptions) ([]*Review, error) {
+	if opts == nil {
+		return nil, fmt.Errorf("opts are nil")
+	}
+
+	sess := db.GetEngine(ctx)
+
+	if opts.IssueID != 0 {
+		sess = sess.Where("issue_id=?", opts.IssueID)
+	}
+	if opts.ReviewerID != 0 {
+		sess = sess.Where("reviewer_id=?", opts.ReviewerID)
+	}
+	if !opts.Dismissed.IsNone() {
+		sess = sess.Where("dismissed=?", opts.Dismissed.IsTrue())
+	}
+
+	reviews := make([]*Review, 0, 4)
+	return reviews, sess.Find(&reviews)
+}
+
 // GetReviewersByIssueID gets the latest review of each reviewer for a pull request
 func GetReviewersByIssueID(issueID int64) ([]*Review, error) {
 	reviews := make([]*Review, 0, 10)
@@ -537,7 +575,7 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
 func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
 	review = new(Review)
 
-	has := false
+	var has bool
 	if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
 		issueID, teamID).
 		Get(review); err != nil {
@@ -548,21 +586,21 @@ func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int6
 		return nil, ErrReviewNotExist{0}
 	}
 
-	return
+	return review, err
 }
 
 // MarkReviewsAsStale marks existing reviews as stale
 func MarkReviewsAsStale(issueID int64) (err error) {
 	_, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID)
 
-	return
+	return err
 }
 
 // MarkReviewsAsNotStale marks existing reviews as not stale for a giving commit SHA
 func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) {
 	_, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID)
 
-	return
+	return err
 }
 
 // DismissReview change the dismiss status of a review
@@ -579,7 +617,7 @@ func DismissReview(review *Review, isDismiss bool) (err error) {
 
 	_, err = db.GetEngine(db.DefaultContext).ID(review.ID).Cols("dismissed").Update(review)
 
-	return
+	return err
 }
 
 // InsertReviews inserts review and review comments
@@ -752,10 +790,10 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
 
 	official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
 	if err != nil {
-		return nil, fmt.Errorf("isOfficialReviewerTeam(): %v", err)
+		return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
 	} else if !official {
 		if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil {
-			return nil, fmt.Errorf("isOfficialReviewer(): %v", err)
+			return nil, fmt.Errorf("isOfficialReviewer(): %w", err)
 		}
 	}
 
@@ -785,7 +823,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *user_
 		ReviewID:        review.ID,
 	})
 	if err != nil {
-		return nil, fmt.Errorf("CreateCommentCtx(): %v", err)
+		return nil, fmt.Errorf("CreateCommentCtx(): %w", err)
 	}
 
 	return comment, committer.Commit()
@@ -814,7 +852,7 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *us
 
 	official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
 	if err != nil {
-		return nil, fmt.Errorf("isOfficialReviewerTeam(): %v", err)
+		return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
 	}
 
 	if official {
@@ -844,7 +882,7 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *organization.Team, doer *us
 		AssigneeTeamID:  reviewer.ID, // Use AssigneeTeamID as reviewer team ID
 	})
 	if err != nil {
-		return nil, fmt.Errorf("CreateCommentCtx(): %v", err)
+		return nil, fmt.Errorf("CreateCommentCtx(): %w", err)
 	}
 
 	return comment, committer.Commit()
diff --git a/models/issues/review_test.go b/models/issues/review_test.go
index 3506604b46dbe..46d1cc777b65b 100644
--- a/models/issues/review_test.go
+++ b/models/issues/review_test.go
@@ -29,22 +29,22 @@ func TestGetReviewByID(t *testing.T) {
 
 func TestReview_LoadAttributes(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 1}).(*issues_model.Review)
+	review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 1})
 	assert.NoError(t, review.LoadAttributes(db.DefaultContext))
 	assert.NotNil(t, review.Issue)
 	assert.NotNil(t, review.Reviewer)
 
-	invalidReview1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 2}).(*issues_model.Review)
+	invalidReview1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 2})
 	assert.Error(t, invalidReview1.LoadAttributes(db.DefaultContext))
 
-	invalidReview2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 3}).(*issues_model.Review)
+	invalidReview2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 3})
 	assert.Error(t, invalidReview2.LoadAttributes(db.DefaultContext))
 }
 
 func TestReview_LoadCodeComments(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 4}).(*issues_model.Review)
+	review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 4})
 	assert.NoError(t, review.LoadAttributes(db.DefaultContext))
 	assert.NoError(t, review.LoadCodeComments(db.DefaultContext))
 	assert.Len(t, review.CodeComments, 1)
@@ -74,8 +74,8 @@ func TestFindReviews(t *testing.T) {
 
 func TestGetCurrentReview(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}).(*issues_model.Issue)
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	review, err := issues_model.GetCurrentReview(db.DefaultContext, user, issue)
 	assert.NoError(t, err)
@@ -83,7 +83,7 @@ func TestGetCurrentReview(t *testing.T) {
 	assert.Equal(t, issues_model.ReviewTypePending, review.Type)
 	assert.Equal(t, "Pending Review", review.Content)
 
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 7}).(*user_model.User)
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 7})
 	review2, err := issues_model.GetCurrentReview(db.DefaultContext, user2, issue)
 	assert.Error(t, err)
 	assert.True(t, issues_model.IsErrReviewNotExist(err))
@@ -93,8 +93,8 @@ func TestGetCurrentReview(t *testing.T) {
 func TestCreateReview(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}).(*issues_model.Issue)
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{
 		Content:  "New Review",
@@ -110,10 +110,10 @@ func TestCreateReview(t *testing.T) {
 func TestGetReviewersByIssueID(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}).(*issues_model.Issue)
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
-	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
 
 	expectedReviews := []*issues_model.Review{}
 	expectedReviews = append(expectedReviews,
@@ -150,43 +150,43 @@ func TestGetReviewersByIssueID(t *testing.T) {
 func TestDismissReview(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	rejectReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
-	approveReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 8}).(*issues_model.Review)
+	rejectReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
+	approveReviewExample := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 8})
 	assert.False(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 	assert.False(t, approveReviewExample.Dismissed)
 
 	assert.NoError(t, issues_model.DismissReview(rejectReviewExample, true))
-	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
+	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
 	assert.True(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 
 	assert.NoError(t, issues_model.DismissReview(requestReviewExample, true))
-	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
+	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
 	assert.True(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 	assert.False(t, approveReviewExample.Dismissed)
 
 	assert.NoError(t, issues_model.DismissReview(requestReviewExample, true))
-	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
+	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
 	assert.True(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 	assert.False(t, approveReviewExample.Dismissed)
 
 	assert.NoError(t, issues_model.DismissReview(requestReviewExample, false))
-	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
+	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
 	assert.True(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 	assert.False(t, approveReviewExample.Dismissed)
 
 	assert.NoError(t, issues_model.DismissReview(requestReviewExample, false))
-	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}).(*issues_model.Review)
-	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}).(*issues_model.Review)
+	rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9})
+	requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11})
 	assert.True(t, rejectReviewExample.Dismissed)
 	assert.False(t, requestReviewExample.Dismissed)
 	assert.False(t, approveReviewExample.Dismissed)
diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go
index e7ac1314e9b5d..a87fbfafa2d21 100644
--- a/models/issues/stopwatch.go
+++ b/models/issues/stopwatch.go
@@ -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"`
@@ -63,7 +71,7 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex
 		Where("user_id = ?", userID).
 		And("issue_id = ?", issueID).
 		Get(sw)
-	return
+	return sw, exists, err
 }
 
 // UserIDCount is a simple coalition of UserID and Count
@@ -130,7 +138,7 @@ func HasUserStopwatch(ctx context.Context, userID int64) (exists bool, sw *Stopw
 	exists, err = db.GetEngine(ctx).
 		Where("user_id = ?", userID).
 		Get(sw)
-	return
+	return exists, sw, err
 }
 
 // FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go
index c0573964d5c40..a5e33f1cf6e2b 100644
--- a/models/issues/stopwatch_test.go
+++ b/models/issues/stopwatch_test.go
@@ -70,7 +70,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
 	assert.NoError(t, err)
 
 	assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(user3, issue1))
-	sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1}).(*issues_model.Stopwatch)
+	sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1})
 	assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow())
 
 	assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(user2, issue2))
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 54179bd3abefe..f2412881ecdb0 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -6,6 +6,7 @@ package issues
 
 import (
 	"context"
+	"errors"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
@@ -47,33 +48,42 @@ func (t *TrackedTime) LoadAttributes() (err error) {
 }
 
 func (t *TrackedTime) loadAttributes(ctx context.Context) (err error) {
+	// Load the issue
 	if t.Issue == nil {
 		t.Issue, err = GetIssueByID(ctx, t.IssueID)
-		if err != nil {
-			return
+
+		if err != nil && !errors.Is(err, util.ErrNotExist) {
+			return err
 		}
+	}
+	// Now load the repo for the issue (which we may have just loaded)
+	if t.Issue != nil {
 		err = t.Issue.LoadRepo(ctx)
-		if err != nil {
-			return
+		if err != nil && !errors.Is(err, util.ErrNotExist) {
+			return err
 		}
 	}
+	// Load the user
 	if t.User == nil {
 		t.User, err = user_model.GetUserByIDCtx(ctx, t.UserID)
 		if err != nil {
-			return
+			if !errors.Is(err, util.ErrNotExist) {
+				return err
+			}
+			t.User = user_model.NewGhostUser()
 		}
 	}
-	return
+	return nil
 }
 
 // LoadAttributes load Issue, User
-func (tl TrackedTimeList) LoadAttributes() (err error) {
+func (tl TrackedTimeList) LoadAttributes() error {
 	for _, t := range tl {
-		if err = t.LoadAttributes(); err != nil {
+		if err := t.LoadAttributes(); err != nil {
 			return err
 		}
 	}
-	return
+	return nil
 }
 
 // FindTrackedTimesOptions represent the filters for tracked times. If an ID is 0 it will be ignored.
@@ -130,7 +140,7 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
 // GetTrackedTimes returns all tracked times that fit to the given options.
 func GetTrackedTimes(ctx context.Context, options *FindTrackedTimesOptions) (trackedTimes TrackedTimeList, err error) {
 	err = options.toSession(db.GetEngine(ctx)).Find(&trackedTimes)
-	return
+	return trackedTimes, err
 }
 
 // CountTrackedTimes returns count of tracked times that fit to the given options.
@@ -236,7 +246,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 {
@@ -291,12 +301,12 @@ func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime
 	}
 
 	_, err = opts.toSession(db.GetEngine(ctx)).Table("tracked_time").Cols("deleted").Update(&TrackedTime{Deleted: true})
-	return
+	return removedTime, err
 }
 
 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 +320,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
 }
diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go
index 787ba9b701e1c..ba8b242d99919 100644
--- a/models/issues/tracked_time_test.go
+++ b/models/issues/tracked_time_test.go
@@ -32,10 +32,10 @@ func TestAddTime(t *testing.T) {
 	assert.Equal(t, int64(1), trackedTime.IssueID)
 	assert.Equal(t, int64(3661), trackedTime.Time)
 
-	tt := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 3, IssueID: 1}).(*issues_model.TrackedTime)
+	tt := unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 3, IssueID: 1})
 	assert.Equal(t, int64(3661), tt.Time)
 
-	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}).(*issues_model.Comment)
+	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeAddTimeManual, PosterID: 3, IssueID: 1})
 	assert.Equal(t, comment.Content, "1 hour 1 minute")
 }
 
diff --git a/models/main_test.go b/models/main_test.go
index bb2fedc15a340..358400156910f 100644
--- a/models/main_test.go
+++ b/models/main_test.go
@@ -7,12 +7,15 @@ package models
 import (
 	"testing"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	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"
 )
 
@@ -28,7 +31,7 @@ func TestFixturesAreConsistent(t *testing.T) {
 		&user_model.User{},
 		&repo_model.Repository{},
 		&organization.Team{},
-		&Action{})
+		&activities_model.Action{})
 }
 
 func TestMain(m *testing.M) {
diff --git a/models/migrate.go b/models/migrate.go
index 0af3891cb858e..d842fb967bfb6 100644
--- a/models/migrate.go
+++ b/models/migrate.go
@@ -9,6 +9,8 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/structs"
 )
 
@@ -98,9 +100,9 @@ func InsertIssueComments(comments []*issues_model.Comment) error {
 		return nil
 	}
 
-	issueIDs := make(map[int64]bool)
+	issueIDs := make(container.Set[int64])
 	for _, comment := range comments {
-		issueIDs[comment.IssueID] = true
+		issueIDs.Add(comment.IssueID)
 	}
 
 	ctx, committer, err := db.TxContext()
@@ -154,7 +156,7 @@ func InsertPullRequests(prs ...*issues_model.PullRequest) error {
 }
 
 // InsertReleases migrates release
-func InsertReleases(rels ...*Release) error {
+func InsertReleases(rels ...*repo_model.Release) error {
 	ctx, committer, err := db.TxContext()
 	if err != nil {
 		return err
@@ -191,7 +193,7 @@ func UpdateMigrationsByType(tp structs.GitServiceType, externalUserID string, us
 		return err
 	}
 
-	if err := UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
+	if err := repo_model.UpdateReleasesMigrationsByType(tp, externalUserID, userID); err != nil {
 		return err
 	}
 
diff --git a/models/migrate_test.go b/models/migrate_test.go
index b6525278ecfa8..bc7729673a55d 100644
--- a/models/migrate_test.go
+++ b/models/migrate_test.go
@@ -21,7 +21,7 @@ import (
 func TestMigrate_InsertMilestones(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	reponame := "repo1"
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
 	name := "milestonetest1"
 	ms := &issues_model.Milestone{
 		RepoID: repo.ID,
@@ -30,7 +30,7 @@ func TestMigrate_InsertMilestones(t *testing.T) {
 	err := InsertMilestones(ms)
 	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, ms)
-	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}).(*repo_model.Repository)
+	repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
 	assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones)
 
 	unittest.CheckConsistencyFor(t, &issues_model.Milestone{})
@@ -39,10 +39,10 @@ func TestMigrate_InsertMilestones(t *testing.T) {
 func assertCreateIssues(t *testing.T, isPull bool) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	reponame := "repo1"
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
-	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
+	milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1})
 	assert.EqualValues(t, milestone.ID, 1)
 	reaction := &issues_model.Reaction{
 		Type:   "heart",
@@ -72,7 +72,7 @@ func assertCreateIssues(t *testing.T, isPull bool) {
 	err := InsertIssues(is)
 	assert.NoError(t, err)
 
-	i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title}).(*issues_model.Issue)
+	i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title})
 	assert.Nil(t, i.ForeignReference)
 	err = i.LoadAttributes(db.DefaultContext)
 	assert.NoError(t, err)
@@ -90,9 +90,9 @@ func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) {
 
 func TestMigrate_InsertIssueComments(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 	_ = issue.LoadRepo(db.DefaultContext)
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID})
 	reaction := &issues_model.Reaction{
 		Type:   "heart",
 		UserID: owner.ID,
@@ -109,7 +109,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
 	err := InsertIssueComments([]*issues_model.Comment{comment})
 	assert.NoError(t, err)
 
-	issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue)
+	issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
 	assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments)
 
 	unittest.CheckConsistencyFor(t, &issues_model.Issue{})
@@ -118,8 +118,8 @@ func TestMigrate_InsertIssueComments(t *testing.T) {
 func TestMigrate_InsertPullRequests(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	reponame := "repo1"
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 
 	i := &issues_model.Issue{
 		RepoID:   repo.ID,
@@ -138,7 +138,7 @@ func TestMigrate_InsertPullRequests(t *testing.T) {
 	err := InsertPullRequests(p)
 	assert.NoError(t, err)
 
-	_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID}).(*issues_model.PullRequest)
+	_ = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{IssueID: i.ID})
 
 	unittest.CheckConsistencyFor(t, &issues_model.Issue{}, &issues_model.PullRequest{})
 }
@@ -149,7 +149,7 @@ func TestMigrate_InsertReleases(t *testing.T) {
 	a := &repo_model.Attachment{
 		UUID: "a0eebc91-9c0c-4ef7-bb6e-6bb9bd380a12",
 	}
-	r := &Release{
+	r := &repo_model.Release{
 		Attachments: []*repo_model.Attachment{a},
 	}
 
diff --git a/models/migrations/fixtures/Test_addConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml b/models/migrations/fixtures/Test_addConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml
new file mode 100644
index 0000000000000..a88c2ef89f088
--- /dev/null
+++ b/models/migrations/fixtures/Test_addConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml
@@ -0,0 +1,2 @@
+-
+  id: 1
diff --git a/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml
new file mode 100644
index 0000000000000..55a237a0d633d
--- /dev/null
+++ b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml
@@ -0,0 +1,9 @@
+-
+  id: 1
+  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+-
+  id: 2
+  credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR="
+-
+  id: 4
+  credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
diff --git a/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml
new file mode 100644
index 0000000000000..c02a76e3742a9
--- /dev/null
+++ b/models/migrations/fixtures/Test_storeWebauthnCredentialIDAsBytes/webauthn_credential.yml
@@ -0,0 +1,31 @@
+-
+  id: 1
+  lower_name: "u2fkey-correctly-migrated"
+  name: "u2fkey-correctly-migrated"
+  user_id: 1
+  credential_id: "TVHE44TOH7DF7V48SEAIT3EMMJ7TGBOQ289E5AQB34S98LFCUFJ7U2NAVI8RJG6K2F4TC8AQ8KBNO7AGEOQOL9NE43GR63HTEHJSLOG="
+  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+  attestation_type: 'fido-u2f'
+  sign_count: 1
+  clone_warning: false
+-
+  id: 2
+  lower_name: "non-u2f-key"
+  name: "non-u2f-key"
+  user_id: 1
+  credential_id: "051CLMMKB62S6M9M2A4H54K7MMCQALFJ36G4TGB2S9A47APLTILU6C6744CEBG4EKCGV357N21BSLH8JD33GQMFAR6DQ70S76P34J6FR"
+  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+  attestation_type: 'none'
+  sign_count: 1
+  clone_warning: false
+-
+  id: 4
+  lower_name: "packed-key"
+  name: "packed-key"
+  user_id: 1
+  credential_id: "APU4B1NDTEVTEM60V4T0FRL7SRJMO9KIE2AKFQ8JDGTQ7VHFI41FDEFTDLBVQEAE4ER49QV2GTGVFDNBO31BPOA3OQN6879OT6MTU3G="
+  public_key: 0x040d0967a2cad045011631187576492a0beb5b377954b4f694c5afc8bdf25270f87f09a9ab6ce9c282f447ba71b2f2bae2105b32b847e0704f310f48644e3eddf2
+  attestation_type: 'fido-u2f'
+  sign_count: 1
+  clone_warning: false
+
diff --git a/models/migrations/fixtures/Test_updateOpenMilestoneCounts/expected_milestone.yml b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/expected_milestone.yml
new file mode 100644
index 0000000000000..9326fa550b148
--- /dev/null
+++ b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/expected_milestone.yml
@@ -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
diff --git a/models/migrations/fixtures/Test_updateOpenMilestoneCounts/issue.yml b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/issue.yml
new file mode 100644
index 0000000000000..fdaacd9f68270
--- /dev/null
+++ b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/issue.yml
@@ -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
diff --git a/models/migrations/fixtures/Test_updateOpenMilestoneCounts/milestone.yml b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/milestone.yml
new file mode 100644
index 0000000000000..0bcf4cffd3aae
--- /dev/null
+++ b/models/migrations/fixtures/Test_updateOpenMilestoneCounts/milestone.yml
@@ -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
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index c843e33eb7805..2e661531b62b8 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -14,6 +14,7 @@ import (
 	"regexp"
 	"strings"
 
+	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 
@@ -56,6 +57,9 @@ type Version struct {
 	Version int64
 }
 
+// Use noopMigration when there is a migration that has been no-oped
+var noopMigration = func(_ *xorm.Engine) error { return nil }
+
 // This is a sequence of migrations. Add new migrations to the bottom of the list.
 // If you want to "retire" a migration, remove it from the top of the list and
 // update minDBVersion accordingly
@@ -351,7 +355,7 @@ var migrations = []Migration{
 	// v198 -> v199
 	NewMigration("Add issue content history table", addTableIssueContentHistory),
 	// v199 -> v200
-	NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop),
+	NewMigration("No-op (remote version is using AppState now)", noopMigration),
 	// v200 -> v201
 	NewMigration("Add table app_state", addTableAppState),
 	// v201 -> v202
@@ -388,19 +392,50 @@ var migrations = []Migration{
 	// v215 -> v216
 	NewMigration("allow to view files in PRs", addReviewViewedFiles),
 	// v216 -> v217
-	NewMigration("Improve Action table indices", improveActionTableIndices),
+	NewMigration("No-op (Improve Action table indices v1)", noopMigration),
+	// v217 -> v218
+	NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText),
+	// v218 -> v219
+	NewMigration("Improve Action table indices v2", improveActionTableIndices),
+	// v219 -> v220
+	NewMigration("Add sync_on_commit column to push_mirror table", addSyncOnCommitColForPushMirror),
+	// v220 -> v221
+	NewMigration("Add container repository property", addContainerRepositoryProperty),
+	// v221 -> v222
+	NewMigration("Store WebAuthentication CredentialID as bytes and increase size to at least 1024", storeWebauthnCredentialIDAsBytes),
+	// v222 -> v223
+	NewMigration("Drop old CredentialID column", dropOldCredentialIDColumn),
+	// v223 -> v224
+	NewMigration("Rename CredentialIDBytes column to CredentialID", renameCredentialIDBytes),
+
+	// Gitea 1.17.0 ends at v224
+
+	// v224 -> v225
+	NewMigration("Add badges to users", createUserBadgesTable),
+	// v225 -> v226
+	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
 func GetCurrentDBVersion(x *xorm.Engine) (int64, error) {
 	if err := x.Sync(new(Version)); err != nil {
-		return -1, fmt.Errorf("sync: %v", err)
+		return -1, fmt.Errorf("sync: %w", err)
 	}
 
 	currentVersion := &Version{ID: 1}
 	has, err := x.Get(currentVersion)
 	if err != nil {
-		return -1, fmt.Errorf("get: %v", err)
+		return -1, fmt.Errorf("get: %w", err)
 	}
 	if !has {
 		return -1, nil
@@ -442,13 +477,13 @@ func Migrate(x *xorm.Engine) error {
 	// Set a new clean the default mapper to GonicMapper as that is the default for Gitea.
 	x.SetMapper(names.GonicMapper{})
 	if err := x.Sync(new(Version)); err != nil {
-		return fmt.Errorf("sync: %v", err)
+		return fmt.Errorf("sync: %w", err)
 	}
 
 	currentVersion := &Version{ID: 1}
 	has, err := x.Get(currentVersion)
 	if err != nil {
-		return fmt.Errorf("get: %v", err)
+		return fmt.Errorf("get: %w", err)
 	} else if !has {
 		// If the version record does not exist we think
 		// it is a fresh installation and we can skip all migrations.
@@ -456,7 +491,7 @@ func Migrate(x *xorm.Engine) error {
 		currentVersion.Version = int64(minDBVersion + len(migrations))
 
 		if _, err = x.InsertOne(currentVersion); err != nil {
-			return fmt.Errorf("insert: %v", err)
+			return fmt.Errorf("insert: %w", err)
 		}
 	}
 
@@ -479,13 +514,20 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t
 		return nil
 	}
 
+	// Some migration tasks depend on the git command
+	if git.DefaultContext == nil {
+		if err = git.InitSimple(context.Background()); err != nil {
+			return err
+		}
+	}
+
 	// Migrate
 	for i, m := range migrations[v-minDBVersion:] {
 		log.Info("Migration[%d]: %s", v+int64(i), m.Description())
 		// Reset the mapper between each migration - migrations are not supposed to depend on each other
 		x.SetMapper(names.GonicMapper{})
 		if err = m.Migrate(x); err != nil {
-			return fmt.Errorf("migration[%d]: %s failed: %v", v+int64(i), m.Description(), err)
+			return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.Description(), err)
 		}
 		currentVersion.Version = v + int64(i) + 1
 		if _, err = x.ID(1).Update(currentVersion); err != nil {
@@ -884,7 +926,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
 			cols += "DROP COLUMN `" + col + "` CASCADE"
 		}
 		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
-			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
+			return fmt.Errorf("Drop table `%s` columns %v: %w", tableName, columnNames, err)
 		}
 	case setting.Database.UseMySQL:
 		// Drop indexes on columns first
@@ -912,7 +954,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
 			cols += "DROP COLUMN `" + col + "`"
 		}
 		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
-			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
+			return fmt.Errorf("Drop table `%s` columns %v: %w", tableName, columnNames, err)
 		}
 	case setting.Database.UseMSSQL:
 		cols := ""
@@ -926,27 +968,27 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
 			tableName, strings.ReplaceAll(cols, "`", "'"))
 		constraints := make([]string, 0)
 		if err := sess.SQL(sql).Find(&constraints); err != nil {
-			return fmt.Errorf("Find constraints: %v", err)
+			return fmt.Errorf("Find constraints: %w", err)
 		}
 		for _, constraint := range constraints {
 			if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
-				return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
+				return fmt.Errorf("Drop table `%s` default constraint `%s`: %w", tableName, constraint, err)
 			}
 		}
 		sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
 			tableName, strings.ReplaceAll(cols, "`", "'"))
 		constraints = make([]string, 0)
 		if err := sess.SQL(sql).Find(&constraints); err != nil {
-			return fmt.Errorf("Find constraints: %v", err)
+			return fmt.Errorf("Find constraints: %w", err)
 		}
 		for _, constraint := range constraints {
 			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
-				return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
+				return fmt.Errorf("Drop index `%s` on `%s`: %w", constraint, tableName, err)
 			}
 		}
 
 		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
-			return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
+			return fmt.Errorf("Drop table `%s` columns %v: %w", tableName, columnNames, err)
 		}
 	default:
 		log.Fatal("Unrecognized DB")
diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go
index 46782f24a1003..5cd70626b46c3 100644
--- a/models/migrations/migrations_test.go
+++ b/models/migrations/migrations_test.go
@@ -46,7 +46,7 @@ func TestMain(m *testing.M) {
 
 	giteaConf := os.Getenv("GITEA_CONF")
 	if giteaConf == "" {
-		giteaConf = path.Join(filepath.Dir(setting.AppPath), "integrations/sqlite.ini")
+		giteaConf = path.Join(filepath.Dir(setting.AppPath), "tests/sqlite.ini")
 		fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
 	}
 
@@ -66,11 +66,10 @@ func TestMain(m *testing.M) {
 
 	setting.SetCustomPathAndConf("", "", "")
 	setting.LoadForTest()
-	if err = git.InitOnceWithSync(context.Background()); err != nil {
-		fmt.Printf("Unable to InitOnceWithSync: %v\n", err)
+	if err = git.InitFull(context.Background()); err != nil {
+		fmt.Printf("Unable to InitFull: %v\n", err)
 		os.Exit(1)
 	}
-	git.CheckLFSVersion()
 	setting.InitDBConfig()
 	setting.NewLogServices(true)
 
@@ -206,8 +205,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
 	ourSkip += skip
 	deferFn := PrintCurrentTest(t, ourSkip)
 	assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
-	assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
-	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
+	assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath))
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	if err != nil {
 		assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
diff --git a/models/migrations/testlogger_test.go b/models/migrations/testlogger_test.go
index adbf19c0dbed6..0455d9c9a6c5b 100644
--- a/models/migrations/testlogger_test.go
+++ b/models/migrations/testlogger_test.go
@@ -188,5 +188,5 @@ func (log *TestLogger) GetName() string {
 func init() {
 	log.Register("test", NewTestLogger)
 	_, filename, _, _ := runtime.Caller(0)
-	prefix = strings.TrimSuffix(filename, "integrations/testlogger.go")
+	prefix = strings.TrimSuffix(filename, "tests/testlogger.go")
 }
diff --git a/models/migrations/v113.go b/models/migrations/v113.go
index 199c02ac98c98..4af246863d0de 100644
--- a/models/migrations/v113.go
+++ b/models/migrations/v113.go
@@ -17,7 +17,7 @@ func featureChangeTargetBranch(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Comment)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v115.go b/models/migrations/v115.go
index 7708ed5e28afd..3e61cb6e0ea28 100644
--- a/models/migrations/v115.go
+++ b/models/migrations/v115.go
@@ -13,6 +13,7 @@ import (
 	"path/filepath"
 	"time"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
@@ -39,16 +40,16 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 	}
 	log.Info("%d User Avatar(s) to migrate ...", count)
 
-	deleteList := make(map[string]struct{})
+	deleteList := make(container.Set[string])
 	start := 0
 	migrated := 0
 	for {
 		if err := sess.Begin(); err != nil {
-			return fmt.Errorf("session.Begin: %v", err)
+			return fmt.Errorf("session.Begin: %w", err)
 		}
 		users := make([]*User, 0, 50)
 		if err := sess.Table("user").Asc("id").Limit(50, start).Find(&users); err != nil {
-			return fmt.Errorf("select users from id [%d]: %v", start, err)
+			return fmt.Errorf("select users from id [%d]: %w", start, err)
 		}
 		if len(users) == 0 {
 			_ = sess.Rollback()
@@ -75,7 +76,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 			newAvatar, err := copyOldAvatarToNewLocation(user.ID, oldAvatar)
 			if err != nil {
 				_ = sess.Rollback()
-				return fmt.Errorf("[user: %s] %v", user.LowerName, err)
+				return fmt.Errorf("[user: %s] %w", user.LowerName, err)
 			} else if newAvatar == oldAvatar {
 				continue
 			}
@@ -83,10 +84,10 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 			user.Avatar = newAvatar
 			if _, err := sess.ID(user.ID).Cols("avatar").Update(user); err != nil {
 				_ = sess.Rollback()
-				return fmt.Errorf("[user: %s] user table update: %v", user.LowerName, err)
+				return fmt.Errorf("[user: %s] user table update: %w", user.LowerName, err)
 			}
 
-			deleteList[filepath.Join(setting.Avatar.Path, oldAvatar)] = struct{}{}
+			deleteList.Add(filepath.Join(setting.Avatar.Path, oldAvatar))
 			migrated++
 			select {
 			case <-ticker.C:
@@ -103,7 +104,7 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 		}
 		if err := sess.Commit(); err != nil {
 			_ = sess.Rollback()
-			return fmt.Errorf("commit session: %v", err)
+			return fmt.Errorf("commit session: %w", err)
 		}
 	}
 
@@ -137,13 +138,13 @@ func renameExistingUserAvatarName(x *xorm.Engine) error {
 func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) {
 	fr, err := os.Open(filepath.Join(setting.Avatar.Path, oldAvatar))
 	if err != nil {
-		return "", fmt.Errorf("os.Open: %v", err)
+		return "", fmt.Errorf("os.Open: %w", err)
 	}
 	defer fr.Close()
 
 	data, err := io.ReadAll(fr)
 	if err != nil {
-		return "", fmt.Errorf("io.ReadAll: %v", err)
+		return "", fmt.Errorf("io.ReadAll: %w", err)
 	}
 
 	newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data)))))
@@ -152,7 +153,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error)
 	}
 
 	if err := os.WriteFile(filepath.Join(setting.Avatar.Path, newAvatar), data, 0o666); err != nil {
-		return "", fmt.Errorf("os.WriteFile: %v", err)
+		return "", fmt.Errorf("os.WriteFile: %w", err)
 	}
 
 	return newAvatar, nil
diff --git a/models/migrations/v125.go b/models/migrations/v125.go
index ac567f66b94f1..64483e1397752 100644
--- a/models/migrations/v125.go
+++ b/models/migrations/v125.go
@@ -17,7 +17,7 @@ func addReviewMigrateInfo(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Review)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v127.go b/models/migrations/v127.go
index d8f0de4a6e102..7be1e326d45af 100644
--- a/models/migrations/v127.go
+++ b/models/migrations/v127.go
@@ -36,10 +36,10 @@ func addLanguageStats(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(LanguageStat)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	if err := x.Sync2(new(RepoIndexerStatus)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v128.go b/models/migrations/v128.go
index 2aa68f9b6474d..7e84ff5b7178c 100644
--- a/models/migrations/v128.go
+++ b/models/migrations/v128.go
@@ -59,7 +59,7 @@ func fixMergeBase(x *xorm.Engine) error {
 	for {
 		prs := make([]PullRequest, 0, 50)
 		if err := x.Limit(limit, start).Asc("id").Find(&prs); err != nil {
-			return fmt.Errorf("Find: %v", err)
+			return fmt.Errorf("Find: %w", err)
 		}
 		if len(prs) == 0 {
 			break
@@ -70,7 +70,7 @@ func fixMergeBase(x *xorm.Engine) error {
 			baseRepo := &Repository{ID: pr.BaseRepoID}
 			has, err := x.Table("repository").Get(baseRepo)
 			if err != nil {
-				return fmt.Errorf("Unable to get base repo %d %v", pr.BaseRepoID, err)
+				return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
 			}
 			if !has {
 				log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
@@ -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
diff --git a/models/migrations/v131.go b/models/migrations/v131.go
index a38c7be634ff5..48fd3e29c9493 100644
--- a/models/migrations/v131.go
+++ b/models/migrations/v131.go
@@ -16,7 +16,7 @@ func addSystemWebhookColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Webhook)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v132.go b/models/migrations/v132.go
index 3f7b1c97096ef..e67a67e907e28 100644
--- a/models/migrations/v132.go
+++ b/models/migrations/v132.go
@@ -16,7 +16,7 @@ func addBranchProtectionProtectedFilesColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(ProtectedBranch)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v134.go b/models/migrations/v134.go
index 16e8ec8e9425b..75c6768720bbf 100644
--- a/models/migrations/v134.go
+++ b/models/migrations/v134.go
@@ -58,7 +58,7 @@ func refixMergeBase(x *xorm.Engine) error {
 	for {
 		prs := make([]PullRequest, 0, 50)
 		if err := x.Limit(limit, start).Asc("id").Where("has_merged = ?", true).Find(&prs); err != nil {
-			return fmt.Errorf("Find: %v", err)
+			return fmt.Errorf("Find: %w", err)
 		}
 		if len(prs) == 0 {
 			break
@@ -69,7 +69,7 @@ func refixMergeBase(x *xorm.Engine) error {
 			baseRepo := &Repository{ID: pr.BaseRepoID}
 			has, err := x.Table("repository").Get(baseRepo)
 			if err != nil {
-				return fmt.Errorf("Unable to get base repo %d %v", pr.BaseRepoID, err)
+				return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
 			}
 			if !has {
 				log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
@@ -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
diff --git a/models/migrations/v135.go b/models/migrations/v135.go
index 8d859d42c05b0..eaa852d44fcdc 100644
--- a/models/migrations/v135.go
+++ b/models/migrations/v135.go
@@ -16,7 +16,7 @@ func addOrgIDLabelColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Label)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v136.go b/models/migrations/v136.go
index 94372e4142562..b2192f38530bb 100644
--- a/models/migrations/v136.go
+++ b/models/migrations/v136.go
@@ -44,7 +44,7 @@ func addCommitDivergenceToPulls(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(PullRequest)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	last := 0
@@ -80,7 +80,7 @@ func addCommitDivergenceToPulls(x *xorm.Engine) error {
 			baseRepo := &Repository{ID: pr.BaseRepoID}
 			has, err := x.Table("repository").Get(baseRepo)
 			if err != nil {
-				return fmt.Errorf("Unable to get base repo %d %v", pr.BaseRepoID, err)
+				return fmt.Errorf("Unable to get base repo %d %w", pr.BaseRepoID, err)
 			}
 			if !has {
 				log.Error("Missing base repo with id %d for PR ID %d", pr.BaseRepoID, pr.ID)
@@ -101,7 +101,7 @@ func addCommitDivergenceToPulls(x *xorm.Engine) error {
 			pr.CommitsBehind = divergence.Behind
 
 			if _, err = sess.ID(pr.ID).Cols("commits_ahead", "commits_behind").Update(pr); err != nil {
-				return fmt.Errorf("Update Cols: %v", err)
+				return fmt.Errorf("Update Cols: %w", err)
 			}
 			migrated++
 		}
diff --git a/models/migrations/v138.go b/models/migrations/v138.go
index 2db9b821add1f..03235200abc85 100644
--- a/models/migrations/v138.go
+++ b/models/migrations/v138.go
@@ -16,7 +16,7 @@ func addResolveDoerIDCommentColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Comment)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v140.go b/models/migrations/v140.go
index 871d14b84eec2..b54740f1a9466 100644
--- a/models/migrations/v140.go
+++ b/models/migrations/v140.go
@@ -34,7 +34,7 @@ func fixLanguageStatsToSaveSize(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(LanguageStat)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	x.Delete(&RepoIndexerStatus{IndexerType: RepoIndexerTypeStats})
diff --git a/models/migrations/v141.go b/models/migrations/v141.go
index ab05698b8cb5d..21247cc78f922 100644
--- a/models/migrations/v141.go
+++ b/models/migrations/v141.go
@@ -16,7 +16,7 @@ func addKeepActivityPrivateUserColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(User)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v143.go b/models/migrations/v143.go
index 93237ebfcd2e0..17f3af44974ae 100644
--- a/models/migrations/v143.go
+++ b/models/migrations/v143.go
@@ -48,5 +48,5 @@ func recalculateStars(x *xorm.Engine) (err error) {
 
 	log.Debug("recalculate Stars number for all user finished")
 
-	return
+	return err
 }
diff --git a/models/migrations/v145.go b/models/migrations/v145.go
index ee79c20e97a1f..afc60497e3184 100644
--- a/models/migrations/v145.go
+++ b/models/migrations/v145.go
@@ -53,16 +53,16 @@ func increaseLanguageField(x *xorm.Engine) error {
 		if err := sess.SQL(`SELECT i.name AS Name
 			FROM sys.indexes i INNER JOIN sys.index_columns ic
       			ON i.index_id = ic.index_id AND i.object_id = ic.object_id
-   			INNER JOIN sys.tables AS t 
+   			INNER JOIN sys.tables AS t
       			ON t.object_id = i.object_id
 			INNER JOIN sys.columns c
 				ON t.object_id = c.object_id AND ic.column_id = c.column_id
 			WHERE t.name = 'language_stat' AND c.name = 'language'`).Find(&constraints); err != nil {
-			return fmt.Errorf("Find constraints: %v", err)
+			return fmt.Errorf("Find constraints: %w", err)
 		}
 		for _, constraint := range constraints {
 			if _, err := sess.Exec(fmt.Sprintf("DROP INDEX [%s] ON `language_stat`", constraint)); err != nil {
-				return fmt.Errorf("Drop table `language_stat` constraint `%s`: %v", constraint, err)
+				return fmt.Errorf("Drop table `language_stat` constraint `%s`: %w", constraint, err)
 			}
 		}
 		if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language %s", sqlType)); err != nil {
diff --git a/models/migrations/v149.go b/models/migrations/v149.go
index 60c0fae8bcdbf..4d2cf5b9767e3 100644
--- a/models/migrations/v149.go
+++ b/models/migrations/v149.go
@@ -19,7 +19,7 @@ func addCreatedAndUpdatedToMilestones(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Milestone)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v154.go b/models/migrations/v154.go
index 11407c30ee140..bb17fb4725a04 100644
--- a/models/migrations/v154.go
+++ b/models/migrations/v154.go
@@ -30,7 +30,7 @@ func addTimeStamps(x *xorm.Engine) error {
 		return err
 	}
 
-	// Follow represents relations of user and his/her followers.
+	// Follow represents relations of user and their followers.
 	type Follow struct {
 		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 	}
diff --git a/models/migrations/v155.go b/models/migrations/v155.go
index 58d78b6cfbd90..f95b4dfa3f5a3 100644
--- a/models/migrations/v155.go
+++ b/models/migrations/v155.go
@@ -16,7 +16,7 @@ func addChangedProtectedFilesPullRequestColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(PullRequest)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v156.go b/models/migrations/v156.go
index 26f97fe984d56..2c146892d2c19 100644
--- a/models/migrations/v156.go
+++ b/models/migrations/v156.go
@@ -123,7 +123,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
 					continue
 				}
 				log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
-				return fmt.Errorf("GetTagCommit: %v", err)
+				return fmt.Errorf("GetTagCommit: %w", err)
 			}
 
 			if commit.Author.Email == "" {
@@ -135,7 +135,7 @@ func fixPublisherIDforTagReleases(x *xorm.Engine) error {
 						continue
 					}
 					log.Error("Error whilst getting commit for Tag: %s in [%d]%s/%s. Error: %v", release.TagName, repo.ID, repo.OwnerName, repo.Name, err)
-					return fmt.Errorf("GetCommit: %v", err)
+					return fmt.Errorf("GetCommit: %w", err)
 				}
 			}
 
diff --git a/models/migrations/v164.go b/models/migrations/v164.go
index 01ba796563d88..02343fac24960 100644
--- a/models/migrations/v164.go
+++ b/models/migrations/v164.go
@@ -32,7 +32,7 @@ func (grant *OAuth2Grant) TableName() string {
 
 func addScopeAndNonceColumnsToOAuth2Grant(x *xorm.Engine) error {
 	if err := x.Sync2(new(OAuth2Grant)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v167.go b/models/migrations/v167.go
index fd91f226abbd9..26d7cfd4f8762 100644
--- a/models/migrations/v167.go
+++ b/models/migrations/v167.go
@@ -18,7 +18,7 @@ func addUserRedirect(x *xorm.Engine) (err error) {
 	}
 
 	if err := x.Sync2(new(UserRedirect)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v170.go b/models/migrations/v170.go
index 853a23d290db7..2d654fb2b1718 100644
--- a/models/migrations/v170.go
+++ b/models/migrations/v170.go
@@ -16,7 +16,7 @@ func addDismissedReviewColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Review)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v171.go b/models/migrations/v171.go
index 2547ff0f77bac..8b27493ceac20 100644
--- a/models/migrations/v171.go
+++ b/models/migrations/v171.go
@@ -16,7 +16,7 @@ func addSortingColToProjectBoard(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(ProjectBoard)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v173.go b/models/migrations/v173.go
index dd4589066d9da..c1f167e6f6a30 100644
--- a/models/migrations/v173.go
+++ b/models/migrations/v173.go
@@ -16,7 +16,7 @@ func addTimeIDCommentColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Comment)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v174.go b/models/migrations/v174.go
index 5915d3626bc78..b6c555525ef94 100644
--- a/models/migrations/v174.go
+++ b/models/migrations/v174.go
@@ -28,7 +28,7 @@ func addRepoTransfer(x *xorm.Engine) error {
 	}
 
 	if err := sess.Sync2(new(RepoTransfer)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	return sess.Commit()
diff --git a/models/migrations/v177.go b/models/migrations/v177.go
index c65fd3de008ea..f28826f17094e 100644
--- a/models/migrations/v177.go
+++ b/models/migrations/v177.go
@@ -25,7 +25,7 @@ func deleteOrphanedIssueLabels(x *xorm.Engine) error {
 	}
 
 	if err := sess.Sync2(new(IssueLabel)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	if _, err := sess.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
diff --git a/models/migrations/v180.go b/models/migrations/v180.go
index 492c91f1b9265..4468a7107801f 100644
--- a/models/migrations/v180.go
+++ b/models/migrations/v180.go
@@ -66,7 +66,7 @@ func deleteMigrationCredentials(x *xorm.Engine) (err error) {
 			return
 		}
 	}
-	return
+	return err
 }
 
 func removeCredentials(payload string) (string, error) {
diff --git a/models/migrations/v183.go b/models/migrations/v183.go
index cc752bf827c1e..0dc3af28a7649 100644
--- a/models/migrations/v183.go
+++ b/models/migrations/v183.go
@@ -32,7 +32,7 @@ func createPushMirrorTable(x *xorm.Engine) error {
 	}
 
 	if err := sess.Sync2(new(PushMirror)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	return sess.Commit()
diff --git a/models/migrations/v184.go b/models/migrations/v184.go
index 97bc72d5d94a2..593a8100a8a5d 100644
--- a/models/migrations/v184.go
+++ b/models/migrations/v184.go
@@ -43,7 +43,7 @@ func renameTaskErrorsToMessage(x *xorm.Engine) error {
 	}
 
 	if err := sess.Sync2(new(Task)); err != nil {
-		return fmt.Errorf("error on Sync2: %v", err)
+		return fmt.Errorf("error on Sync2: %w", err)
 	}
 
 	if messageExist {
diff --git a/models/migrations/v189.go b/models/migrations/v189.go
index f136a89b4ef83..823e27e2ea0f1 100644
--- a/models/migrations/v189.go
+++ b/models/migrations/v189.go
@@ -81,7 +81,7 @@ func unwrapLDAPSourceCfg(x *xorm.Engine) error {
 			}
 			err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped)
 			if err != nil {
-				return fmt.Errorf("failed to unmarshal %s: %w", string(source.Cfg), err)
+				return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err)
 			}
 			if wrapped.Source != nil && len(wrapped.Source) > 0 {
 				bs, err := json.Marshal(wrapped.Source)
diff --git a/models/migrations/v190.go b/models/migrations/v190.go
index 8d1fba83734f4..00046ff2a18e1 100644
--- a/models/migrations/v190.go
+++ b/models/migrations/v190.go
@@ -18,7 +18,7 @@ func addAgitFlowPullRequest(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(PullRequest)); err != nil {
-		return fmt.Errorf("sync2: %v", err)
+		return fmt.Errorf("sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v194.go b/models/migrations/v194.go
index 7ea160e2b26bd..6bd2f19ef5318 100644
--- a/models/migrations/v194.go
+++ b/models/migrations/v194.go
@@ -16,7 +16,7 @@ func addBranchProtectionUnprotectedFilesColumn(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(ProtectedBranch)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v195.go b/models/migrations/v195.go
index 06694eb57df9f..8594dddf1fc18 100644
--- a/models/migrations/v195.go
+++ b/models/migrations/v195.go
@@ -20,7 +20,7 @@ func addTableCommitStatusIndex(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(CommitStatusIndex)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	sess := x.NewSession()
diff --git a/models/migrations/v196.go b/models/migrations/v196.go
index c0332c7bb4ee5..0423d0268b789 100644
--- a/models/migrations/v196.go
+++ b/models/migrations/v196.go
@@ -16,7 +16,7 @@ func addColorColToProjectBoard(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(ProjectBoard)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v198.go b/models/migrations/v198.go
index e3c31460a9fc7..4b1515109e1d2 100644
--- a/models/migrations/v198.go
+++ b/models/migrations/v198.go
@@ -27,7 +27,7 @@ func addTableIssueContentHistory(x *xorm.Engine) error {
 	sess := x.NewSession()
 	defer sess.Close()
 	if err := sess.Sync2(new(IssueContentHistory)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return sess.Commit()
 }
diff --git a/models/migrations/v199.go b/models/migrations/v199.go
index 4351ba4fa80bd..29f9d49dbeaee 100644
--- a/models/migrations/v199.go
+++ b/models/migrations/v199.go
@@ -4,11 +4,4 @@
 
 package migrations
 
-import (
-	"xorm.io/xorm"
-)
-
-func addRemoteVersionTableNoop(x *xorm.Engine) error {
-	// we used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
-	return nil
-}
+// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
diff --git a/models/migrations/v200.go b/models/migrations/v200.go
index 56ac06cb13662..f0f107bf77d75 100644
--- a/models/migrations/v200.go
+++ b/models/migrations/v200.go
@@ -17,7 +17,7 @@ func addTableAppState(x *xorm.Engine) error {
 		Content  string `xorm:"LONGTEXT"`
 	}
 	if err := x.Sync2(new(AppState)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v202.go b/models/migrations/v202.go
index 664728969aea1..1bfc28d637029 100644
--- a/models/migrations/v202.go
+++ b/models/migrations/v202.go
@@ -18,7 +18,7 @@ func createUserSettingsTable(x *xorm.Engine) error {
 		SettingValue string `xorm:"text"`
 	}
 	if err := x.Sync2(new(UserSetting)); err != nil {
-		return fmt.Errorf("sync2: %v", err)
+		return fmt.Errorf("sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v206.go b/models/migrations/v206.go
index c6a5dc811c59b..525a47572218b 100644
--- a/models/migrations/v206.go
+++ b/models/migrations/v206.go
@@ -20,7 +20,7 @@ func addAuthorizeColForTeamUnit(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(TeamUnit)); err != nil {
-		return fmt.Errorf("sync2: %v", err)
+		return fmt.Errorf("sync2: %w", err)
 	}
 
 	// migrate old permission
diff --git a/models/migrations/v210.go b/models/migrations/v210.go
index f32fae77bacc9..891c96fb3031d 100644
--- a/models/migrations/v210.go
+++ b/models/migrations/v210.go
@@ -144,7 +144,7 @@ func remigrateU2FCredentials(x *xorm.Engine) error {
 				if !has {
 					has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential))
 					if err != nil {
-						return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id:%v]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
+						return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id: %d]. Error: %w", remigrated.LowerName, remigrated.UserID, err)
 					}
 					if !has {
 						_, err = sess.Insert(remigrated)
diff --git a/models/migrations/v211.go b/models/migrations/v211.go
index 26ccfd20376c1..ec7cb46d472fc 100644
--- a/models/migrations/v211.go
+++ b/models/migrations/v211.go
@@ -20,7 +20,7 @@ func createForeignReferenceTable(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(ForeignReference)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v216.go b/models/migrations/v216.go
index b011c11d95e32..ab44808402e9b 100644
--- a/models/migrations/v216.go
+++ b/models/migrations/v216.go
@@ -4,64 +4,5 @@
 
 package migrations
 
-import (
-	"code.gitea.io/gitea/modules/timeutil"
-
-	"xorm.io/xorm"
-	"xorm.io/xorm/schemas"
-)
-
-type improveActionTableIndicesAction struct {
-	ID          int64 `xorm:"pk autoincr"`
-	UserID      int64 // Receiver user id.
-	OpType      int
-	ActUserID   int64 // Action user id.
-	RepoID      int64
-	CommentID   int64 `xorm:"INDEX"`
-	IsDeleted   bool  `xorm:"NOT NULL DEFAULT false"`
-	RefName     string
-	IsPrivate   bool               `xorm:"NOT NULL DEFAULT false"`
-	Content     string             `xorm:"TEXT"`
-	CreatedUnix timeutil.TimeStamp `xorm:"created"`
-}
-
-// TableName sets the name of this table
-func (a *improveActionTableIndicesAction) TableName() string {
-	return "action"
-}
-
-// TableIndices implements xorm's TableIndices interface
-func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index {
-	actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
-	actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
-
-	repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
-	repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
-
-	return []*schemas.Index{actUserIndex, repoIndex}
-}
-
-func improveActionTableIndices(x *xorm.Engine) error {
-	{
-		type Action struct {
-			ID          int64 `xorm:"pk autoincr"`
-			UserID      int64 `xorm:"INDEX"` // Receiver user id.
-			OpType      int
-			ActUserID   int64 `xorm:"INDEX"` // Action user id.
-			RepoID      int64 `xorm:"INDEX"`
-			CommentID   int64 `xorm:"INDEX"`
-			IsDeleted   bool  `xorm:"INDEX NOT NULL DEFAULT false"`
-			RefName     string
-			IsPrivate   bool               `xorm:"INDEX NOT NULL DEFAULT false"`
-			Content     string             `xorm:"TEXT"`
-			CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
-		}
-		if err := x.Sync2(&Action{}); err != nil {
-			return err
-		}
-		if err := x.DropIndexes(&Action{}); err != nil {
-			return err
-		}
-	}
-	return x.Sync2(&improveActionTableIndicesAction{})
-}
+// This migration added non-ideal indices to the action table which on larger datasets slowed things down
+// it has been superceded by v218.go
diff --git a/models/migrations/v217.go b/models/migrations/v217.go
new file mode 100644
index 0000000000000..280dba17a917e
--- /dev/null
+++ b/models/migrations/v217.go
@@ -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/setting"
+
+	"xorm.io/xorm"
+)
+
+func alterHookTaskTextFieldsToLongText(x *xorm.Engine) error {
+	sess := x.NewSession()
+	defer sess.Close()
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+
+	if setting.Database.UseMySQL {
+		if _, err := sess.Exec("ALTER TABLE `hook_task` CHANGE `payload_content` `payload_content` LONGTEXT, CHANGE `request_content` `request_content` LONGTEXT, change `response_content` `response_content` LONGTEXT"); err != nil {
+			return err
+		}
+	}
+	return sess.Commit()
+}
diff --git a/models/migrations/v218.go b/models/migrations/v218.go
new file mode 100644
index 0000000000000..e08c6c5b0f5ec
--- /dev/null
+++ b/models/migrations/v218.go
@@ -0,0 +1,53 @@
+// 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/setting"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+	"xorm.io/xorm/schemas"
+)
+
+type improveActionTableIndicesAction struct {
+	ID          int64 `xorm:"pk autoincr"`
+	UserID      int64 // Receiver user id.
+	OpType      int
+	ActUserID   int64 // Action user id.
+	RepoID      int64
+	CommentID   int64 `xorm:"INDEX"`
+	IsDeleted   bool  `xorm:"NOT NULL DEFAULT false"`
+	RefName     string
+	IsPrivate   bool               `xorm:"NOT NULL DEFAULT false"`
+	Content     string             `xorm:"TEXT"`
+	CreatedUnix timeutil.TimeStamp `xorm:"created"`
+}
+
+// TableName sets the name of this table
+func (*improveActionTableIndicesAction) TableName() string {
+	return "action"
+}
+
+// TableIndices implements xorm's TableIndices interface
+func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
+	repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
+	repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
+
+	actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
+	actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
+	indices := []*schemas.Index{actUserIndex, repoIndex}
+	if setting.Database.UsePostgreSQL {
+		cudIndex := schemas.NewIndex("c_u_d", schemas.IndexType)
+		cudIndex.AddColumn("created_unix", "user_id", "is_deleted")
+		indices = append(indices, cudIndex)
+	}
+
+	return indices
+}
+
+func improveActionTableIndices(x *xorm.Engine) error {
+	return x.Sync2(&improveActionTableIndicesAction{})
+}
diff --git a/models/migrations/v219.go b/models/migrations/v219.go
new file mode 100644
index 0000000000000..7b4f34b704033
--- /dev/null
+++ b/models/migrations/v219.go
@@ -0,0 +1,31 @@
+// 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 (
+	"time"
+
+	"code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+func addSyncOnCommitColForPushMirror(x *xorm.Engine) error {
+	type PushMirror struct {
+		ID         int64            `xorm:"pk autoincr"`
+		RepoID     int64            `xorm:"INDEX"`
+		Repo       *repo.Repository `xorm:"-"`
+		RemoteName string
+
+		SyncOnCommit   bool `xorm:"NOT NULL DEFAULT true"`
+		Interval       time.Duration
+		CreatedUnix    timeutil.TimeStamp `xorm:"created"`
+		LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
+		LastError      string             `xorm:"text"`
+	}
+
+	return x.Sync2(new(PushMirror))
+}
diff --git a/models/migrations/v220.go b/models/migrations/v220.go
new file mode 100644
index 0000000000000..8138bc5bb1499
--- /dev/null
+++ b/models/migrations/v220.go
@@ -0,0 +1,28 @@
+// 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 (
+	packages_model "code.gitea.io/gitea/models/packages"
+	container_module "code.gitea.io/gitea/modules/packages/container"
+
+	"xorm.io/xorm"
+	"xorm.io/xorm/schemas"
+)
+
+func addContainerRepositoryProperty(x *xorm.Engine) (err error) {
+	switch x.Dialect().URI().DBType {
+	case schemas.SQLITE:
+		_, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?",
+			packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
+	case schemas.MSSQL:
+		_, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name + '/' + p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?",
+			packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
+	default:
+		_, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?",
+			packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer)
+	}
+	return err
+}
diff --git a/models/migrations/v221.go b/models/migrations/v221.go
new file mode 100644
index 0000000000000..f3bcfcdf1de20
--- /dev/null
+++ b/models/migrations/v221.go
@@ -0,0 +1,75 @@
+// 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 (
+	"encoding/base32"
+	"fmt"
+
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+func storeWebauthnCredentialIDAsBytes(x *xorm.Engine) error {
+	// Create webauthnCredential table
+	type webauthnCredential struct {
+		ID           int64 `xorm:"pk autoincr"`
+		Name         string
+		LowerName    string `xorm:"unique(s)"`
+		UserID       int64  `xorm:"INDEX unique(s)"`
+		CredentialID string `xorm:"INDEX VARCHAR(410)"`
+		// Note the lack of INDEX here - these will be created once the column is renamed in v223.go
+		CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+		PublicKey         []byte
+		AttestationType   string
+		AAGUID            []byte
+		SignCount         uint32 `xorm:"BIGINT"`
+		CloneWarning      bool
+		CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
+	}
+	if err := x.Sync2(&webauthnCredential{}); err != nil {
+		return err
+	}
+
+	var start int
+	creds := make([]*webauthnCredential, 0, 50)
+	for {
+		err := x.Select("id, credential_id").OrderBy("id").Limit(50, start).Find(&creds)
+		if err != nil {
+			return err
+		}
+
+		err = func() error {
+			sess := x.NewSession()
+			defer sess.Close()
+			if err := sess.Begin(); err != nil {
+				return fmt.Errorf("unable to allow start session. Error: %w", err)
+			}
+			for _, cred := range creds {
+				cred.CredentialIDBytes, err = base32.HexEncoding.DecodeString(cred.CredentialID)
+				if err != nil {
+					return fmt.Errorf("unable to parse credential id %s for credential[%d]: %w", cred.CredentialID, cred.ID, err)
+				}
+				count, err := sess.ID(cred.ID).Cols("credential_id_bytes").Update(cred)
+				if count != 1 || err != nil {
+					return fmt.Errorf("unable to update credential id bytes for credential[%d]: %d,%w", cred.ID, count, err)
+				}
+			}
+			return sess.Commit()
+		}()
+		if err != nil {
+			return err
+		}
+
+		if len(creds) < 50 {
+			break
+		}
+		start += 50
+		creds = creds[:0]
+	}
+	return nil
+}
diff --git a/models/migrations/v221_test.go b/models/migrations/v221_test.go
new file mode 100644
index 0000000000000..c50ca5c873291
--- /dev/null
+++ b/models/migrations/v221_test.go
@@ -0,0 +1,65 @@
+// 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 (
+	"encoding/base32"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_storeWebauthnCredentialIDAsBytes(t *testing.T) {
+	// Create webauthnCredential table
+	type WebauthnCredential struct {
+		ID              int64 `xorm:"pk autoincr"`
+		Name            string
+		LowerName       string `xorm:"unique(s)"`
+		UserID          int64  `xorm:"INDEX unique(s)"`
+		CredentialID    string `xorm:"INDEX VARCHAR(410)"`
+		PublicKey       []byte
+		AttestationType string
+		AAGUID          []byte
+		SignCount       uint32 `xorm:"BIGINT"`
+		CloneWarning    bool
+	}
+
+	type ExpectedWebauthnCredential struct {
+		ID           int64  `xorm:"pk autoincr"`
+		CredentialID string // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+	}
+
+	type ConvertedWebauthnCredential struct {
+		ID                int64  `xorm:"pk autoincr"`
+		CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+	}
+
+	// Prepare and load the testing database
+	x, deferable := prepareTestEnv(t, 0, new(WebauthnCredential), new(ExpectedWebauthnCredential))
+	defer deferable()
+	if x == nil || t.Failed() {
+		return
+	}
+
+	if err := storeWebauthnCredentialIDAsBytes(x); err != nil {
+		assert.NoError(t, err)
+		return
+	}
+
+	expected := []ExpectedWebauthnCredential{}
+	if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) {
+		return
+	}
+
+	got := []ConvertedWebauthnCredential{}
+	if err := x.Table("webauthn_credential").Select("id, credential_id_bytes").Asc("id").Find(&got); !assert.NoError(t, err) {
+		return
+	}
+
+	for i, e := range expected {
+		credIDBytes, _ := base32.HexEncoding.DecodeString(e.CredentialID)
+		assert.Equal(t, credIDBytes, got[i].CredentialIDBytes)
+	}
+}
diff --git a/models/migrations/v222.go b/models/migrations/v222.go
new file mode 100644
index 0000000000000..99acdfd20608a
--- /dev/null
+++ b/models/migrations/v222.go
@@ -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 (
+	"context"
+	"fmt"
+
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+func dropOldCredentialIDColumn(x *xorm.Engine) error {
+	// This migration maybe rerun so that we should check if it has been run
+	credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
+	if err != nil {
+		return err
+	}
+	if !credentialIDExist {
+		// Column is already non-extant
+		return nil
+	}
+	credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
+	if err != nil {
+		return err
+	}
+	if !credentialIDBytesExists {
+		// looks like 221 hasn't properly run
+		return fmt.Errorf("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration")
+	}
+
+	// Create webauthnCredential table
+	type webauthnCredential struct {
+		ID           int64 `xorm:"pk autoincr"`
+		Name         string
+		LowerName    string `xorm:"unique(s)"`
+		UserID       int64  `xorm:"INDEX unique(s)"`
+		CredentialID string `xorm:"INDEX VARCHAR(410)"`
+		// Note the lack of the INDEX on CredentialIDBytes - we will add this in v223.go
+		CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+		PublicKey         []byte
+		AttestationType   string
+		AAGUID            []byte
+		SignCount         uint32 `xorm:"BIGINT"`
+		CloneWarning      bool
+		CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
+	}
+	if err := x.Sync2(&webauthnCredential{}); err != nil {
+		return err
+	}
+
+	// Drop the old credential ID
+	sess := x.NewSession()
+	defer sess.Close()
+
+	if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
+		return fmt.Errorf("unable to drop old credentialID column: %w", err)
+	}
+	return sess.Commit()
+}
diff --git a/models/migrations/v223.go b/models/migrations/v223.go
new file mode 100644
index 0000000000000..9f4c6acfe332e
--- /dev/null
+++ b/models/migrations/v223.go
@@ -0,0 +1,103 @@
+// 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 (
+	"context"
+	"fmt"
+
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+func renameCredentialIDBytes(x *xorm.Engine) error {
+	// This migration maybe rerun so that we should check if it has been run
+	credentialIDExist, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id")
+	if err != nil {
+		return err
+	}
+	if credentialIDExist {
+		credentialIDBytesExists, err := x.Dialect().IsColumnExist(x.DB(), context.Background(), "webauthn_credential", "credential_id_bytes")
+		if err != nil {
+			return err
+		}
+		if !credentialIDBytesExists {
+			return nil
+		}
+	}
+
+	err = func() error {
+		// webauthnCredential table
+		type webauthnCredential struct {
+			ID        int64 `xorm:"pk autoincr"`
+			Name      string
+			LowerName string `xorm:"unique(s)"`
+			UserID    int64  `xorm:"INDEX unique(s)"`
+			// Note the lack of INDEX here
+			CredentialIDBytes []byte `xorm:"VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+			PublicKey         []byte
+			AttestationType   string
+			AAGUID            []byte
+			SignCount         uint32 `xorm:"BIGINT"`
+			CloneWarning      bool
+			CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
+			UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
+		}
+		sess := x.NewSession()
+		defer sess.Close()
+		if err := sess.Begin(); err != nil {
+			return err
+		}
+
+		if err := sess.Sync2(new(webauthnCredential)); err != nil {
+			return fmt.Errorf("error on Sync2: %w", err)
+		}
+
+		if credentialIDExist {
+			// if both errors and message exist, drop message at first
+			if err := dropTableColumns(sess, "webauthn_credential", "credential_id"); err != nil {
+				return err
+			}
+		}
+
+		switch {
+		case setting.Database.UseMySQL:
+			if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil {
+				return err
+			}
+		case setting.Database.UseMSSQL:
+			if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil {
+				return err
+			}
+		default:
+			if _, err := sess.Exec("ALTER TABLE `webauthn_credential` RENAME COLUMN credential_id_bytes TO credential_id"); err != nil {
+				return err
+			}
+		}
+		return sess.Commit()
+	}()
+	if err != nil {
+		return err
+	}
+
+	// Create webauthnCredential table
+	type webauthnCredential struct {
+		ID              int64 `xorm:"pk autoincr"`
+		Name            string
+		LowerName       string `xorm:"unique(s)"`
+		UserID          int64  `xorm:"INDEX unique(s)"`
+		CredentialID    []byte `xorm:"INDEX VARBINARY(1024)"` // CredentialID is at most 1023 bytes as per spec released 20 July 2022
+		PublicKey       []byte
+		AttestationType string
+		AAGUID          []byte
+		SignCount       uint32 `xorm:"BIGINT"`
+		CloneWarning    bool
+		CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"`
+	}
+	return x.Sync2(&webauthnCredential{})
+}
diff --git a/models/migrations/v224.go b/models/migrations/v224.go
new file mode 100644
index 0000000000000..2ed161ef4da7f
--- /dev/null
+++ b/models/migrations/v224.go
@@ -0,0 +1,28 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"xorm.io/xorm"
+)
+
+func createUserBadgesTable(x *xorm.Engine) error {
+	type Badge struct {
+		ID          int64 `xorm:"pk autoincr"`
+		Description string
+		ImageURL    string
+	}
+
+	type userBadge struct {
+		ID      int64 `xorm:"pk autoincr"`
+		BadgeID int64
+		UserID  int64 `xorm:"INDEX"`
+	}
+
+	if err := x.Sync2(new(Badge)); err != nil {
+		return err
+	}
+	return x.Sync2(new(userBadge))
+}
diff --git a/models/migrations/v225.go b/models/migrations/v225.go
new file mode 100644
index 0000000000000..6dd460eb68a9e
--- /dev/null
+++ b/models/migrations/v225.go
@@ -0,0 +1,29 @@
+// 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/setting"
+
+	"xorm.io/xorm"
+)
+
+func alterPublicGPGKeyContentFieldsToMediumText(x *xorm.Engine) error {
+	sess := x.NewSession()
+	defer sess.Close()
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+
+	if setting.Database.UseMySQL {
+		if _, err := sess.Exec("ALTER TABLE `gpg_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
+			return err
+		}
+		if _, err := sess.Exec("ALTER TABLE `public_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil {
+			return err
+		}
+	}
+	return sess.Commit()
+}
diff --git a/models/migrations/v226.go b/models/migrations/v226.go
new file mode 100644
index 0000000000000..2f85bca21f677
--- /dev/null
+++ b/models/migrations/v226.go
@@ -0,0 +1,15 @@
+// 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/builder"
+	"xorm.io/xorm"
+)
+
+func fixPackageSemverField(x *xorm.Engine) error {
+	_, err := x.Exec(builder.Update(builder.Eq{"semver_compatible": false}).From("`package`").Where(builder.In("`type`", "conan", "generic")))
+	return err
+}
diff --git a/models/migrations/v227.go b/models/migrations/v227.go
new file mode 100644
index 0000000000000..36c0a5eef1387
--- /dev/null
+++ b/models/migrations/v227.go
@@ -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: %w", 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)
+}
diff --git a/models/migrations/v228.go b/models/migrations/v228.go
new file mode 100644
index 0000000000000..62c81ef9d8c24
--- /dev/null
+++ b/models/migrations/v228.go
@@ -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))
+}
diff --git a/models/migrations/v229.go b/models/migrations/v229.go
new file mode 100644
index 0000000000000..42ec2033fee2f
--- /dev/null
+++ b/models/migrations/v229.go
@@ -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
+}
diff --git a/models/migrations/v229_test.go b/models/migrations/v229_test.go
new file mode 100644
index 0000000000000..f8a147c9bd69a
--- /dev/null
+++ b/models/migrations/v229_test.go
@@ -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)
+	}
+}
diff --git a/models/migrations/v230.go b/models/migrations/v230.go
new file mode 100644
index 0000000000000..f08e6a37641fc
--- /dev/null
+++ b/models/migrations/v230.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"xorm.io/xorm"
+)
+
+// 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))
+}
diff --git a/models/migrations/v230_test.go b/models/migrations/v230_test.go
new file mode 100644
index 0000000000000..98ba3f5d97209
--- /dev/null
+++ b/models/migrations/v230_test.go
@@ -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)
+	}
+}
diff --git a/models/migrations/v70.go b/models/migrations/v70.go
index 7d34c89d11294..b2563544b2d1f 100644
--- a/models/migrations/v70.go
+++ b/models/migrations/v70.go
@@ -38,7 +38,7 @@ func addIssueDependencies(x *xorm.Engine) (err error) {
 	)
 
 	if err = x.Sync(new(IssueDependency)); err != nil {
-		return fmt.Errorf("Error creating issue_dependency_table column definition: %v", err)
+		return fmt.Errorf("Error creating issue_dependency_table column definition: %w", err)
 	}
 
 	// Update Comment definition
@@ -76,7 +76,7 @@ func addIssueDependencies(x *xorm.Engine) (err error) {
 	}
 
 	if err = x.Sync(new(Comment)); err != nil {
-		return fmt.Errorf("Error updating issue_comment table column definition: %v", err)
+		return fmt.Errorf("Error updating issue_comment table column definition: %w", err)
 	}
 
 	// RepoUnit describes all units of a repository
@@ -93,7 +93,7 @@ func addIssueDependencies(x *xorm.Engine) (err error) {
 	units := make([]*RepoUnit, 0, 100)
 	err = x.Where("`type` = ?", v16UnitTypeIssues).Find(&units)
 	if err != nil {
-		return fmt.Errorf("Query repo units: %v", err)
+		return fmt.Errorf("Query repo units: %w", err)
 	}
 	for _, unit := range units {
 		if unit.Config == nil {
diff --git a/models/migrations/v71.go b/models/migrations/v71.go
index 163ec3ee5f988..70314386d7116 100644
--- a/models/migrations/v71.go
+++ b/models/migrations/v71.go
@@ -30,7 +30,7 @@ func addScratchHash(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(TwoFactor)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	sess := x.NewSession()
@@ -61,7 +61,7 @@ func addScratchHash(x *xorm.Engine) error {
 			tfa.ScratchHash = hashToken(tfa.ScratchToken, salt)
 
 			if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil {
-				return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %v", err)
+				return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err)
 			}
 
 		}
diff --git a/models/migrations/v72.go b/models/migrations/v72.go
index 612f58aab5ef5..2be4233863ff9 100644
--- a/models/migrations/v72.go
+++ b/models/migrations/v72.go
@@ -25,7 +25,7 @@ func addReview(x *xorm.Engine) error {
 	}
 
 	if err := x.Sync2(new(Review)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return nil
 }
diff --git a/models/migrations/v76.go b/models/migrations/v76.go
index a82ae40ba7637..2686422723f28 100644
--- a/models/migrations/v76.go
+++ b/models/migrations/v76.go
@@ -43,7 +43,7 @@ func addPullRequestRebaseWithMerge(x *xorm.Engine) error {
 	// Updating existing issue units
 	units := make([]*RepoUnit, 0, 100)
 	if err := sess.Where("`type` = ?", v16UnitTypePRs).Find(&units); err != nil {
-		return fmt.Errorf("Query repo units: %v", err)
+		return fmt.Errorf("Query repo units: %w", err)
 	}
 	for _, unit := range units {
 		if unit.Config == nil {
diff --git a/models/migrations/v81.go b/models/migrations/v81.go
index 4e9e7658ee4c1..5141f975764fc 100644
--- a/models/migrations/v81.go
+++ b/models/migrations/v81.go
@@ -24,7 +24,7 @@ func changeU2FCounterType(x *xorm.Engine) error {
 	}
 
 	if err != nil {
-		return fmt.Errorf("Error changing u2f_registration counter column type: %v", err)
+		return fmt.Errorf("Error changing u2f_registration counter column type: %w", err)
 	}
 
 	return nil
diff --git a/models/migrations/v85.go b/models/migrations/v85.go
index 9611d6e72ac28..317660eb6f049 100644
--- a/models/migrations/v85.go
+++ b/models/migrations/v85.go
@@ -41,7 +41,7 @@ func hashAppToken(x *xorm.Engine) error {
 	}
 
 	if err := sess.Sync2(new(AccessToken)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 
 	if err := sess.Commit(); err != nil {
@@ -79,7 +79,7 @@ func hashAppToken(x *xorm.Engine) error {
 			token.Sha1 = "" // ensure to blank out column in case drop column doesn't work
 
 			if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil {
-				return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %v", err)
+				return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err)
 			}
 
 		}
@@ -113,7 +113,7 @@ func resyncHashAppTokenWithUniqueHash(x *xorm.Engine) error {
 		return err
 	}
 	if err := sess.Sync2(new(AccessToken)); err != nil {
-		return fmt.Errorf("Sync2: %v", err)
+		return fmt.Errorf("Sync2: %w", err)
 	}
 	return sess.Commit()
 }
diff --git a/models/org.go b/models/org.go
index 849c9b985b38a..150c41f55da91 100644
--- a/models/org.go
+++ b/models/org.go
@@ -8,79 +8,13 @@ package models
 import (
 	"context"
 	"fmt"
-	"strings"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/models/unit"
-	user_model "code.gitea.io/gitea/models/user"
-
-	"xorm.io/builder"
 )
 
-// MinimalOrg represents a simple orgnization with only needed columns
-type MinimalOrg = organization.Organization
-
-// GetUserOrgsList returns one user's all orgs list
-func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) {
-	schema, err := db.TableInfo(new(user_model.User))
-	if err != nil {
-		return nil, err
-	}
-
-	outputCols := []string{
-		"id",
-		"name",
-		"full_name",
-		"visibility",
-		"avatar",
-		"avatar_email",
-		"use_custom_avatar",
-	}
-
-	groupByCols := &strings.Builder{}
-	for _, col := range outputCols {
-		fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col)
-	}
-	groupByStr := groupByCols.String()
-	groupByStr = groupByStr[0 : len(groupByStr)-1]
-
-	sess := db.GetEngine(db.DefaultContext)
-	sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count").
-		Table("user").
-		Join("INNER", "team", "`team`.org_id = `user`.id").
-		Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
-		Join("LEFT", builder.
-			Select("id as repo_id, owner_id as repo_owner_id").
-			From("repository").
-			Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id").
-		Where("`team_user`.uid = ?", user.ID).
-		GroupBy(groupByStr)
-
-	type OrgCount struct {
-		organization.Organization `xorm:"extends"`
-		OrgCount                  int
-	}
-
-	orgCounts := make([]*OrgCount, 0, 10)
-
-	if err := sess.
-		Asc("`user`.name").
-		Find(&orgCounts); err != nil {
-		return nil, err
-	}
-
-	orgs := make([]*MinimalOrg, len(orgCounts))
-	for i, orgCount := range orgCounts {
-		orgCount.Organization.NumRepos = orgCount.OrgCount
-		orgs[i] = &orgCount.Organization
-	}
-
-	return orgs, nil
-}
-
 func removeOrgUser(ctx context.Context, orgID, userID int64) error {
 	ou := new(organization.OrgUser)
 
@@ -91,14 +25,14 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
 		And("org_id=?", orgID).
 		Get(ou)
 	if err != nil {
-		return fmt.Errorf("get org-user: %v", err)
+		return fmt.Errorf("get org-user: %w", err)
 	} else if !has {
 		return nil
 	}
 
 	org, err := organization.GetOrgByID(ctx, orgID)
 	if err != nil {
-		return fmt.Errorf("GetUserByID [%d]: %v", orgID, err)
+		return fmt.Errorf("GetUserByID [%d]: %w", orgID, err)
 	}
 
 	// Check if the user to delete is the last member in owner team.
@@ -128,11 +62,11 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
 	// Delete all repository accesses and unwatch them.
 	env, err := organization.AccessibleReposEnv(ctx, org, userID)
 	if err != nil {
-		return fmt.Errorf("AccessibleReposEnv: %v", err)
+		return fmt.Errorf("AccessibleReposEnv: %w", err)
 	}
 	repoIDs, err := env.RepoIDs(1, org.NumRepos)
 	if err != nil {
-		return fmt.Errorf("GetUserRepositories [%d]: %v", userID, err)
+		return fmt.Errorf("GetUserRepositories [%d]: %w", userID, err)
 	}
 	for _, repoID := range repoIDs {
 		if err = repo_model.WatchRepo(ctx, userID, repoID, false); err != nil {
@@ -149,7 +83,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
 		}
 	}
 
-	// Delete member in his/her teams.
+	// Delete member in their teams.
 	teams, err := organization.GetUserOrgTeams(ctx, org.ID, userID)
 	if err != nil {
 		return err
diff --git a/models/org_team.go b/models/org_team.go
index 5d29e333373ad..290b1c8b6ada1 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -25,29 +25,29 @@ import (
 	"xorm.io/builder"
 )
 
-func addRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
+func AddRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
 	if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil {
 		return err
 	}
 
-	if _, err = db.GetEngine(ctx).Incr("num_repos").ID(t.ID).Update(new(organization.Team)); err != nil {
-		return fmt.Errorf("update team: %v", err)
+	if err = organization.IncrTeamRepoNum(ctx, t.ID); err != nil {
+		return fmt.Errorf("update team: %w", err)
 	}
 
 	t.NumRepos++
 
 	if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
-		return fmt.Errorf("recalculateAccesses: %v", err)
+		return fmt.Errorf("recalculateAccesses: %w", err)
 	}
 
 	// Make all team members watch this repo if enabled in global settings
 	if setting.Service.AutoWatchNewRepos {
 		if err = t.GetMembersCtx(ctx); err != nil {
-			return fmt.Errorf("getMembers: %v", err)
+			return fmt.Errorf("getMembers: %w", err)
 		}
 		for _, u := range t.Members {
 			if err = repo_model.WatchRepo(ctx, u.ID, repo.ID, true); err != nil {
-				return fmt.Errorf("watchRepo: %v", err)
+				return fmt.Errorf("watchRepo: %w", err)
 			}
 		}
 	}
@@ -58,16 +58,15 @@ func addRepository(ctx context.Context, t *organization.Team, repo *repo_model.R
 // addAllRepositories adds all repositories to the team.
 // If the team already has some repositories they will be left unchanged.
 func addAllRepositories(ctx context.Context, t *organization.Team) error {
-	var orgRepos []repo_model.Repository
-	e := db.GetEngine(ctx)
-	if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
-		return fmt.Errorf("get org repos: %v", err)
+	orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID)
+	if err != nil {
+		return fmt.Errorf("get org repos: %w", err)
 	}
 
 	for _, repo := range orgRepos {
 		if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
-			if err := addRepository(ctx, t, &repo); err != nil {
-				return fmt.Errorf("addRepository: %v", err)
+			if err := AddRepository(ctx, t, repo); err != nil {
+				return fmt.Errorf("AddRepository: %w", err)
 			}
 		}
 	}
@@ -90,27 +89,6 @@ func AddAllRepositories(t *organization.Team) (err error) {
 	return committer.Commit()
 }
 
-// AddRepository adds new repository to team of organization.
-func AddRepository(t *organization.Team, repo *repo_model.Repository) (err error) {
-	if repo.OwnerID != t.OrgID {
-		return errors.New("Repository does not belong to organization")
-	} else if HasRepository(t, repo.ID) {
-		return nil
-	}
-
-	ctx, committer, err := db.TxContext()
-	if err != nil {
-		return err
-	}
-	defer committer.Close()
-
-	if err = addRepository(ctx, t, repo); err != nil {
-		return err
-	}
-
-	return committer.Commit()
-}
-
 // RemoveAllRepositories removes all repositories from team and recalculates access
 func RemoveAllRepositories(t *organization.Team) (err error) {
 	if t.IncludesAllRepositories {
@@ -202,7 +180,7 @@ func removeRepository(ctx context.Context, t *organization.Team, repo *repo_mode
 
 	teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID)
 	if err != nil {
-		return fmt.Errorf("getTeamUsersByTeamID: %v", err)
+		return fmt.Errorf("getTeamUsersByTeamID: %w", err)
 	}
 	for _, teamUser := range teamUsers {
 		has, err := access_model.HasAccess(ctx, teamUser.UID, repo)
@@ -309,7 +287,7 @@ func NewTeam(t *organization.Team) (err error) {
 	if t.IncludesAllRepositories {
 		err = addAllRepositories(ctx, t)
 		if err != nil {
-			return fmt.Errorf("addAllRepositories: %v", err)
+			return fmt.Errorf("addAllRepositories: %w", err)
 		}
 	}
 
@@ -351,7 +329,7 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err
 
 	if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
 		"can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
-		return fmt.Errorf("update: %v", err)
+		return fmt.Errorf("update: %w", err)
 	}
 
 	// update units for team
@@ -373,12 +351,12 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err
 	// Update access for team members if needed.
 	if authChanged {
 		if err = t.GetRepositoriesCtx(ctx); err != nil {
-			return fmt.Errorf("getRepositories: %v", err)
+			return fmt.Errorf("getRepositories: %w", err)
 		}
 
 		for _, repo := range t.Repos {
 			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
-				return fmt.Errorf("recalculateTeamAccesses: %v", err)
+				return fmt.Errorf("recalculateTeamAccesses: %w", err)
 			}
 		}
 	}
@@ -387,7 +365,7 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err
 	if includeAllChanged && t.IncludesAllRepositories {
 		err = addAllRepositories(ctx, t)
 		if err != nil {
-			return fmt.Errorf("addAllRepositories: %v", err)
+			return fmt.Errorf("addAllRepositories: %w", err)
 		}
 	}
 
@@ -419,7 +397,7 @@ func DeleteTeam(t *organization.Team) error {
 			builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
 			Find(&protections)
 		if err != nil {
-			return fmt.Errorf("findProtectedBranches: %v", err)
+			return fmt.Errorf("findProtectedBranches: %w", err)
 		}
 		for _, p := range protections {
 			var matched1, matched2, matched3 bool
@@ -441,7 +419,7 @@ func DeleteTeam(t *organization.Team) error {
 					"merge_whitelist_team_i_ds",
 					"approvals_whitelist_team_i_ds",
 				).Update(p); err != nil {
-					return fmt.Errorf("updateProtectedBranches: %v", err)
+					return fmt.Errorf("updateProtectedBranches: %w", err)
 				}
 			}
 		}
@@ -453,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 {
-		return err
-	}
-
-	// Delete team-unit.
-	if _, err := sess.
-		Where("team_id=?", t.ID).
-		Delete(new(organization.TeamUnit)); 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.
-	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
@@ -528,14 +496,14 @@ func AddTeamMember(team *organization.Team, userID int64) error {
 		And("mode < ?", team.AccessMode).
 		SetExpr("mode", team.AccessMode).
 		Update(new(access_model.Access)); err != nil {
-		return fmt.Errorf("update user accesses: %v", err)
+		return fmt.Errorf("update user accesses: %w", err)
 	}
 
 	// for not exist access
 	var repoIDs []int64
 	accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
 	if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
-		return fmt.Errorf("select id accesses: %v", err)
+		return fmt.Errorf("select id accesses: %w", err)
 	}
 
 	accesses := make([]*access_model.Access, 0, 100)
@@ -543,7 +511,7 @@ func AddTeamMember(team *organization.Team, userID int64) error {
 		accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
 		if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
 			if err = db.Insert(ctx, accesses); err != nil {
-				return fmt.Errorf("insert new user accesses: %v", err)
+				return fmt.Errorf("insert new user accesses: %w", err)
 			}
 			accesses = accesses[:0]
 		}
diff --git a/models/org_team_test.go b/models/org_team_test.go
index 35ee9af85a8e2..a600d07c0c9b3 100644
--- a/models/org_team_test.go
+++ b/models/org_team_test.go
@@ -23,7 +23,7 @@ func TestTeam_AddMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID, userID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, AddTeamMember(team, userID))
 		unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
 		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
@@ -37,7 +37,7 @@ func TestTeam_RemoveMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	testSuccess := func(teamID, userID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, RemoveTeamMember(team, userID))
 		unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
 		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
@@ -47,7 +47,7 @@ func TestTeam_RemoveMember(t *testing.T) {
 	testSuccess(3, 2)
 	testSuccess(3, unittest.NonexistentID)
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
 	err := RemoveTeamMember(team, 2)
 	assert.True(t, organization.IsErrLastOrgOwner(err))
 }
@@ -56,7 +56,7 @@ func TestTeam_HasRepository(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID, repoID int64, expected bool) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.Equal(t, expected, HasRepository(team, repoID))
 	}
 	test(1, 1, false)
@@ -68,30 +68,11 @@ func TestTeam_HasRepository(t *testing.T) {
 	test(2, 5, false)
 }
 
-func TestTeam_AddRepository(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	testSuccess := func(teamID, repoID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
-		assert.NoError(t, AddRepository(team, repo))
-		unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID})
-		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID})
-	}
-	testSuccess(2, 3)
-	testSuccess(2, 5)
-
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	assert.Error(t, AddRepository(team, repo))
-	unittest.CheckConsistencyFor(t, &organization.Team{ID: 1}, &repo_model.Repository{ID: 1})
-}
-
 func TestTeam_RemoveRepository(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	testSuccess := func(teamID, repoID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, RemoveRepository(team, repoID))
 		unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID})
 		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID})
@@ -120,17 +101,17 @@ func TestUpdateTeam(t *testing.T) {
 	// successful update
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
 	team.LowerName = "newname"
 	team.Name = "newName"
 	team.Description = strings.Repeat("A long description!", 100)
 	team.AccessMode = perm.AccessModeAdmin
 	assert.NoError(t, UpdateTeam(team, true, false))
 
-	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}).(*organization.Team)
+	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"})
 	assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
 
-	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}).(*access_model.Access)
+	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3})
 	assert.EqualValues(t, perm.AccessModeAdmin, access.Mode)
 
 	unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
@@ -140,7 +121,7 @@ func TestUpdateTeam2(t *testing.T) {
 	// update to already-existing team
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
 	team.LowerName = "owners"
 	team.Name = "Owners"
 	team.Description = strings.Repeat("A long description!", 100)
@@ -153,15 +134,15 @@ func TestUpdateTeam2(t *testing.T) {
 func TestDeleteTeam(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
 	assert.NoError(t, DeleteTeam(team))
 	unittest.AssertNotExistsBean(t, &organization.Team{ID: team.ID})
 	unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID})
 	unittest.AssertNotExistsBean(t, &organization.TeamUser{TeamID: team.ID})
 
 	// check that team members don't have "leftover" access to repos
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 	accessMode, err := access_model.AccessLevel(user, repo)
 	assert.NoError(t, err)
 	assert.True(t, accessMode < perm.AccessModeWrite)
@@ -171,7 +152,7 @@ func TestAddTeamMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID, userID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, AddTeamMember(team, userID))
 		unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
 		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &user_model.User{ID: team.OrgID})
@@ -185,7 +166,7 @@ func TestRemoveTeamMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	testSuccess := func(teamID, userID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, RemoveTeamMember(team, userID))
 		unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID, TeamID: teamID})
 		unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID})
@@ -195,15 +176,15 @@ func TestRemoveTeamMember(t *testing.T) {
 	testSuccess(3, 2)
 	testSuccess(3, unittest.NonexistentID)
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
 	err := RemoveTeamMember(team, 2)
 	assert.True(t, organization.IsErrLastOrgOwner(err))
 }
 
 func TestRepository_RecalculateAccesses3(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team)
-	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User)
+	team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5})
+	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
 
 	has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
 	assert.NoError(t, err)
diff --git a/models/org_test.go b/models/org_test.go
index af11bed280f4d..23b417119ede1 100644
--- a/models/org_test.go
+++ b/models/org_test.go
@@ -16,14 +16,14 @@ import (
 
 func TestUser_RemoveMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 
 	// remove a user that is a member
 	unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
 	prevNumMembers := org.NumMembers
 	assert.NoError(t, RemoveOrgUser(org.ID, 4))
 	unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 4, OrgID: 3})
-	org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	assert.Equal(t, prevNumMembers-1, org.NumMembers)
 
 	// remove a user that is not a member
@@ -31,7 +31,7 @@ func TestUser_RemoveMember(t *testing.T) {
 	prevNumMembers = org.NumMembers
 	assert.NoError(t, RemoveOrgUser(org.ID, 5))
 	unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: 5, OrgID: 3})
-	org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	assert.Equal(t, prevNumMembers, org.NumMembers)
 
 	unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
@@ -40,14 +40,14 @@ func TestUser_RemoveMember(t *testing.T) {
 func TestRemoveOrgUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	testSuccess := func(orgID, userID int64) {
-		org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User)
+		org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
 		expectedNumMembers := org.NumMembers
 		if unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) {
 			expectedNumMembers--
 		}
 		assert.NoError(t, RemoveOrgUser(orgID, userID))
 		unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: orgID, UID: userID})
-		org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User)
+		org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
 		assert.EqualValues(t, expectedNumMembers, org.NumMembers)
 	}
 	testSuccess(3, 4)
diff --git a/models/organization/mini_org.go b/models/organization/mini_org.go
new file mode 100644
index 0000000000000..36cf948e654ca
--- /dev/null
+++ b/models/organization/mini_org.go
@@ -0,0 +1,78 @@
+// 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 (
+	"fmt"
+	"strings"
+
+	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/models/unit"
+	user_model "code.gitea.io/gitea/models/user"
+
+	"xorm.io/builder"
+)
+
+// MinimalOrg represents a simple organization with only the needed columns
+type MinimalOrg = Organization
+
+// GetUserOrgsList returns all organizations the given user has access to
+func GetUserOrgsList(user *user_model.User) ([]*MinimalOrg, error) {
+	schema, err := db.TableInfo(new(user_model.User))
+	if err != nil {
+		return nil, err
+	}
+
+	outputCols := []string{
+		"id",
+		"name",
+		"full_name",
+		"visibility",
+		"avatar",
+		"avatar_email",
+		"use_custom_avatar",
+	}
+
+	groupByCols := &strings.Builder{}
+	for _, col := range outputCols {
+		fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col)
+	}
+	groupByStr := groupByCols.String()
+	groupByStr = groupByStr[0 : len(groupByStr)-1]
+
+	sess := db.GetEngine(db.DefaultContext)
+	sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count").
+		Table("user").
+		Join("INNER", "team", "`team`.org_id = `user`.id").
+		Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
+		Join("LEFT", builder.
+			Select("id as repo_id, owner_id as repo_owner_id").
+			From("repository").
+			Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id").
+		Where("`team_user`.uid = ?", user.ID).
+		GroupBy(groupByStr)
+
+	type OrgCount struct {
+		Organization `xorm:"extends"`
+		OrgCount     int
+	}
+
+	orgCounts := make([]*OrgCount, 0, 10)
+
+	if err := sess.
+		Asc("`user`.name").
+		Find(&orgCounts); err != nil {
+		return nil, err
+	}
+
+	orgs := make([]*MinimalOrg, len(orgCounts))
+	for i, orgCount := range orgCounts {
+		orgCount.Organization.NumRepos = orgCount.OrgCount
+		orgs[i] = &orgCount.Organization
+	}
+
+	return orgs, nil
+}
diff --git a/models/organization/org.go b/models/organization/org.go
index 0d4a5e337b626..217fa623bf8f0 100644
--- a/models/organization/org.go
+++ b/models/organization/org.go
@@ -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
 
@@ -279,10 +288,10 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
 	}
 
 	if err = db.Insert(ctx, org); err != nil {
-		return fmt.Errorf("insert organization: %v", err)
+		return fmt.Errorf("insert organization: %w", err)
 	}
 	if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil {
-		return fmt.Errorf("generate random avatar: %v", err)
+		return fmt.Errorf("generate random avatar: %w", err)
 	}
 
 	// Add initial creator to organization and owner team.
@@ -290,7 +299,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
 		UID:   owner.ID,
 		OrgID: org.ID,
 	}); err != nil {
-		return fmt.Errorf("insert org-user relation: %v", err)
+		return fmt.Errorf("insert org-user relation: %w", err)
 	}
 
 	// Create default owner team.
@@ -304,7 +313,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
 		CanCreateOrgRepo:        true,
 	}
 	if err = db.Insert(ctx, t); err != nil {
-		return fmt.Errorf("insert owner team: %v", err)
+		return fmt.Errorf("insert owner team: %w", err)
 	}
 
 	// insert units for team
@@ -326,7 +335,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
 		OrgID:  org.ID,
 		TeamID: t.ID,
 	}); err != nil {
-		return fmt.Errorf("insert team-user relation: %v", err)
+		return fmt.Errorf("insert team-user relation: %w", err)
 	}
 
 	return committer.Commit()
@@ -361,12 +370,13 @@ 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: %w", err)
 	}
 
 	if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil {
-		return fmt.Errorf("Delete: %v", err)
+		return fmt.Errorf("Delete: %w", err)
 	}
 
 	return nil
@@ -448,8 +458,9 @@ func CountOrgs(opts FindOrgOptions) (int64, error) {
 
 // HasOrgOrUserVisible tells if the given user can see the given org or user
 func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
-	// Not SignedUser
-	if user == nil {
+	// If user is nil, it's an anonymous user/request.
+	// The Ghost user is handled like an anonymous user.
+	if user == nil || user.IsGhost() {
 		return orgOrUser.Visibility == structs.VisibleTypePublic
 	}
 
@@ -680,7 +691,7 @@ type accessibleReposEnv struct {
 	user    *user_model.User
 	team    *Team
 	teamIDs []int64
-	e       db.Engine
+	ctx     context.Context
 	keyword string
 	orderBy db.SearchOrderBy
 }
@@ -706,7 +717,7 @@ func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (A
 		org:     org,
 		user:    user,
 		teamIDs: teamIDs,
-		e:       db.GetEngine(ctx),
+		ctx:     ctx,
 		orderBy: db.SearchOrderByRecentUpdated,
 	}, nil
 }
@@ -717,7 +728,7 @@ func (org *Organization) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvir
 	return &accessibleReposEnv{
 		org:     org,
 		team:    team,
-		e:       db.GetEngine(db.DefaultContext),
+		ctx:     db.DefaultContext,
 		orderBy: db.SearchOrderByRecentUpdated,
 	}
 }
@@ -744,13 +755,13 @@ func (env *accessibleReposEnv) cond() builder.Cond {
 }
 
 func (env *accessibleReposEnv) CountRepos() (int64, error) {
-	repoCount, err := env.e.
+	repoCount, err := db.GetEngine(env.ctx).
 		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
 		Where(env.cond()).
 		Distinct("`repository`.id").
 		Count(&repo_model.Repository{})
 	if err != nil {
-		return 0, fmt.Errorf("count user repositories in organization: %v", err)
+		return 0, fmt.Errorf("count user repositories in organization: %w", err)
 	}
 	return repoCount, nil
 }
@@ -761,7 +772,7 @@ func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) {
 	}
 
 	repoIDs := make([]int64, 0, pageSize)
-	return repoIDs, env.e.
+	return repoIDs, db.GetEngine(env.ctx).
 		Table("repository").
 		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
 		Where(env.cond()).
@@ -775,7 +786,7 @@ func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) {
 func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*repo_model.Repository, error) {
 	repoIDs, err := env.RepoIDs(page, pageSize)
 	if err != nil {
-		return nil, fmt.Errorf("GetUserRepositoryIDs: %v", err)
+		return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
 	}
 
 	repos := make([]*repo_model.Repository, 0, len(repoIDs))
@@ -783,7 +794,7 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*repo_model.Reposito
 		return repos, nil
 	}
 
-	return repos, env.e.
+	return repos, db.GetEngine(env.ctx).
 		In("`repository`.id", repoIDs).
 		OrderBy(string(env.orderBy)).
 		Find(&repos)
@@ -791,7 +802,7 @@ func (env *accessibleReposEnv) Repos(page, pageSize int) ([]*repo_model.Reposito
 
 func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) {
 	repoIDs := make([]int64, 0, 10)
-	return repoIDs, env.e.
+	return repoIDs, db.GetEngine(env.ctx).
 		Table("repository").
 		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true).
 		Where(env.cond()).
@@ -804,7 +815,7 @@ func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) {
 func (env *accessibleReposEnv) MirrorRepos() ([]*repo_model.Repository, error) {
 	repoIDs, err := env.MirrorRepoIDs()
 	if err != nil {
-		return nil, fmt.Errorf("MirrorRepoIDs: %v", err)
+		return nil, fmt.Errorf("MirrorRepoIDs: %w", err)
 	}
 
 	repos := make([]*repo_model.Repository, 0, len(repoIDs))
@@ -812,7 +823,7 @@ func (env *accessibleReposEnv) MirrorRepos() ([]*repo_model.Repository, error) {
 		return repos, nil
 	}
 
-	return repos, env.e.
+	return repos, db.GetEngine(env.ctx).
 		In("`repository`.id", repoIDs).
 		Find(&repos)
 }
diff --git a/models/organization/org_repo.go b/models/organization/org_repo.go
new file mode 100644
index 0000000000000..364374f71b34d
--- /dev/null
+++ b/models/organization/org_repo.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package organization
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
+)
+
+// GetOrgRepositories get repos belonging to the given organization
+func GetOrgRepositories(ctx context.Context, orgID int64) ([]*repo_model.Repository, error) {
+	var orgRepos []*repo_model.Repository
+	return orgRepos, db.GetEngine(ctx).Where("owner_id = ?", orgID).Find(&orgRepos)
+}
diff --git a/models/organization/org_test.go b/models/organization/org_test.go
index 3a135498a3378..0fba6e25925cc 100644
--- a/models/organization/org_test.go
+++ b/models/organization/org_test.go
@@ -31,7 +31,7 @@ func TestUser_IsOwnedBy(t *testing.T) {
 		{2, 2, false}, // user2 is not an organization
 		{2, 3, false},
 	} {
-		org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID}).(*organization.Organization)
+		org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID})
 		isOwner, err := org.IsOwnedBy(testCase.UserID)
 		assert.NoError(t, err)
 		assert.Equal(t, testCase.ExpectedOwner, isOwner)
@@ -52,7 +52,7 @@ func TestUser_IsOrgMember(t *testing.T) {
 		{2, 2, false}, // user2 is not an organization
 		{2, 3, false},
 	} {
-		org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID}).(*organization.Organization)
+		org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: testCase.OrgID})
 		isMember, err := org.IsOrgMember(testCase.UserID)
 		assert.NoError(t, err)
 		assert.Equal(t, testCase.ExpectedMember, isMember)
@@ -61,7 +61,7 @@ func TestUser_IsOrgMember(t *testing.T) {
 
 func TestUser_GetTeam(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	team, err := org.GetTeam("team1")
 	assert.NoError(t, err)
 	assert.Equal(t, org.ID, team.OrgID)
@@ -70,26 +70,26 @@ func TestUser_GetTeam(t *testing.T) {
 	_, err = org.GetTeam("does not exist")
 	assert.True(t, organization.IsErrTeamNotExist(err))
 
-	nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2}).(*organization.Organization)
+	nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2})
 	_, err = nonOrg.GetTeam("team")
 	assert.True(t, organization.IsErrTeamNotExist(err))
 }
 
 func TestUser_GetOwnerTeam(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	team, err := org.GetOwnerTeam()
 	assert.NoError(t, err)
 	assert.Equal(t, org.ID, team.OrgID)
 
-	nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2}).(*organization.Organization)
+	nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2})
 	_, err = nonOrg.GetOwnerTeam()
 	assert.True(t, organization.IsErrTeamNotExist(err))
 }
 
 func TestUser_GetTeams(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	teams, err := org.LoadTeams()
 	assert.NoError(t, err)
 	if assert.Len(t, teams, 4) {
@@ -102,7 +102,7 @@ func TestUser_GetTeams(t *testing.T) {
 
 func TestUser_GetMembers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	members, _, err := org.GetMembers()
 	assert.NoError(t, err)
 	if assert.Len(t, members, 3) {
@@ -275,7 +275,7 @@ func TestChangeOrgUserStatus(t *testing.T) {
 
 	testSuccess := func(orgID, userID int64, public bool) {
 		assert.NoError(t, organization.ChangeOrgUserStatus(orgID, userID, public))
-		orgUser := unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: orgID, UID: userID}).(*organization.OrgUser)
+		orgUser := unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: orgID, UID: userID})
 		assert.Equal(t, public, orgUser.IsPublic)
 	}
 
@@ -287,7 +287,7 @@ func TestChangeOrgUserStatus(t *testing.T) {
 
 func TestUser_GetUserTeamIDs(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	testSuccess := func(userID int64, expected []int64) {
 		teamIDs, err := org.GetUserTeamIDs(userID)
 		assert.NoError(t, err)
@@ -300,7 +300,7 @@ func TestUser_GetUserTeamIDs(t *testing.T) {
 
 func TestAccessibleReposEnv_CountRepos(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	testSuccess := func(userID, expectedCount int64) {
 		env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
 		assert.NoError(t, err)
@@ -314,7 +314,7 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
 
 func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) {
 		env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
 		assert.NoError(t, err)
@@ -328,7 +328,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
 
 func TestAccessibleReposEnv_Repos(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	testSuccess := func(userID int64, expectedRepoIDs []int64) {
 		env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
 		assert.NoError(t, err)
@@ -337,7 +337,7 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
 		expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs))
 		for i, repoID := range expectedRepoIDs {
 			expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
-				&repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+				&repo_model.Repository{ID: repoID})
 		}
 		assert.Equal(t, expectedRepos, repos)
 	}
@@ -347,7 +347,7 @@ func TestAccessibleReposEnv_Repos(t *testing.T) {
 
 func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 	testSuccess := func(userID int64, expectedRepoIDs []int64) {
 		env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
 		assert.NoError(t, err)
@@ -356,7 +356,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
 		expectedRepos := make([]*repo_model.Repository, len(expectedRepoIDs))
 		for i, repoID := range expectedRepoIDs {
 			expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
-				&repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+				&repo_model.Repository{ID: repoID})
 		}
 		assert.Equal(t, expectedRepos, repos)
 	}
@@ -366,8 +366,8 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
 
 func TestHasOrgVisibleTypePublic(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
 
 	const newOrgName = "test-org-public"
 	org := &organization.Organization{
@@ -378,7 +378,7 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
 	unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
 	assert.NoError(t, organization.CreateOrganization(org, owner))
 	org = unittest.AssertExistsAndLoadBean(t,
-		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*organization.Organization)
+		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
 	test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
 	test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
 	test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
@@ -389,8 +389,8 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
 
 func TestHasOrgVisibleTypeLimited(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
 
 	const newOrgName = "test-org-limited"
 	org := &organization.Organization{
@@ -401,7 +401,7 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) {
 	unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
 	assert.NoError(t, organization.CreateOrganization(org, owner))
 	org = unittest.AssertExistsAndLoadBean(t,
-		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*organization.Organization)
+		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
 	test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
 	test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
 	test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
@@ -412,8 +412,8 @@ func TestHasOrgVisibleTypeLimited(t *testing.T) {
 
 func TestHasOrgVisibleTypePrivate(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
 
 	const newOrgName = "test-org-private"
 	org := &organization.Organization{
@@ -424,7 +424,7 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) {
 	unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization})
 	assert.NoError(t, organization.CreateOrganization(org, owner))
 	org = unittest.AssertExistsAndLoadBean(t,
-		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}).(*organization.Organization)
+		&organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization})
 	test1 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), owner)
 	test2 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user3)
 	test3 := organization.HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), nil)
@@ -453,8 +453,8 @@ func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) {
 
 func TestUser_RemoveOrgRepo(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: org.ID}).(*repo_model.Repository)
+	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: org.ID})
 
 	// remove a repo that does belong to org
 	unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{RepoID: repo.ID, OrgID: org.ID})
@@ -478,7 +478,7 @@ func TestCreateOrganization(t *testing.T) {
 	// successful creation of org
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	const newOrgName = "neworg"
 	org := &organization.Organization{
 		Name: newOrgName,
@@ -487,9 +487,9 @@ func TestCreateOrganization(t *testing.T) {
 	unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization})
 	assert.NoError(t, organization.CreateOrganization(org, owner))
 	org = unittest.AssertExistsAndLoadBean(t,
-		&organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}).(*organization.Organization)
+		&organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization})
 	ownerTeam := unittest.AssertExistsAndLoadBean(t,
-		&organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID}).(*organization.Team)
+		&organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID})
 	unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID})
 	unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
 }
@@ -498,7 +498,7 @@ func TestCreateOrganization2(t *testing.T) {
 	// unauthorized creation of org
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
 	const newOrgName = "neworg"
 	org := &organization.Organization{
 		Name: newOrgName,
@@ -516,7 +516,7 @@ func TestCreateOrganization3(t *testing.T) {
 	// create org with same name as existent org
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	org := &organization.Organization{Name: "user3"}                      // should already exist
 	unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check
 	err := organization.CreateOrganization(org, owner)
@@ -529,7 +529,7 @@ func TestCreateOrganization4(t *testing.T) {
 	// create org with unusable name
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	err := organization.CreateOrganization(&organization.Organization{Name: "assets"}, owner)
 	assert.Error(t, err)
 	assert.True(t, db.IsErrNameReserved(err))
diff --git a/models/organization/org_user.go b/models/organization/org_user.go
index a7bc8f7d4c1a8..7a5d17a75a7e5 100644
--- a/models/organization/org_user.go
+++ b/models/organization/org_user.go
@@ -118,7 +118,7 @@ func loadOrganizationOwners(ctx context.Context, users user_model.UserList, orgI
 		And("team_id=?", ownerTeam.ID).
 		Find(&ownerMaps)
 	if err != nil {
-		return nil, fmt.Errorf("find team users: %v", err)
+		return nil, fmt.Errorf("find team users: %w", err)
 	}
 	return ownerMaps, nil
 }
diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go
index 22ee5217f95e2..aed3ea23cf8cd 100644
--- a/models/organization/org_user_test.go
+++ b/models/organization/org_user_test.go
@@ -130,7 +130,7 @@ func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bo
 func TestAddOrgUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	testSuccess := func(orgID, userID int64, isPublic bool) {
-		org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User)
+		org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
 		expectedNumMembers := org.NumMembers
 		if !unittest.BeanExists(t, &organization.OrgUser{OrgID: orgID, UID: userID}) {
 			expectedNumMembers++
@@ -139,7 +139,7 @@ func TestAddOrgUser(t *testing.T) {
 		ou := &organization.OrgUser{OrgID: orgID, UID: userID}
 		unittest.AssertExistsAndLoadBean(t, ou)
 		assert.Equal(t, isPublic, ou.IsPublic)
-		org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}).(*user_model.User)
+		org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID})
 		assert.EqualValues(t, expectedNumMembers, org.NumMembers)
 	}
 
diff --git a/models/organization/team.go b/models/organization/team.go
index b32ffa6ca7681..aa9b24b57f439 100644
--- a/models/organization/team.go
+++ b/models/organization/team.go
@@ -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
@@ -96,16 +106,7 @@ type SearchTeamOptions struct {
 	IncludeDesc bool
 }
 
-// SearchTeam search for teams. Caller is responsible to check permissions.
-func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
-	if opts.Page <= 0 {
-		opts.Page = 1
-	}
-	if opts.PageSize == 0 {
-		// Default limit
-		opts.PageSize = 10
-	}
-
+func (opts *SearchTeamOptions) toCond() builder.Cond {
 	cond := builder.NewCond()
 
 	if len(opts.Keyword) > 0 {
@@ -117,28 +118,32 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
 		cond = cond.And(keywordCond)
 	}
 
-	cond = cond.And(builder.Eq{"org_id": opts.OrgID})
+	if opts.OrgID > 0 {
+		cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID})
+	}
+
+	if opts.UserID > 0 {
+		cond = cond.And(builder.Eq{"team_user.uid": opts.UserID})
+	}
 
+	return cond
+}
+
+// SearchTeam search for teams. Caller is responsible to check permissions.
+func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
 	sess := db.GetEngine(db.DefaultContext)
 
-	count, err := sess.
-		Where(cond).
-		Count(new(Team))
-	if err != nil {
-		return nil, 0, err
-	}
+	opts.SetDefaultValues()
+	cond := opts.toCond()
 
-	sess = sess.Where(cond)
-	if opts.PageSize == -1 {
-		opts.PageSize = int(count)
-	} else {
-		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
+	if opts.UserID > 0 {
+		sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id")
 	}
+	sess = db.SetSessionPagination(sess, opts)
 
 	teams := make([]*Team, 0, opts.PageSize)
-	if err = sess.
-		OrderBy("lower_name").
-		Find(&teams); err != nil {
+	count, err := sess.Where(cond).OrderBy("lower_name").FindAndCount(&teams)
+	if err != nil {
 		return nil, 0, err
 	}
 
@@ -185,7 +190,7 @@ func (t *Team) GetUnitNames() (res []string) {
 	for _, u := range t.Units {
 		res = append(res, unit.Units[u.Type].NameKey)
 	}
-	return
+	return res
 }
 
 // GetUnitsMap returns the team units permissions
@@ -226,7 +231,7 @@ func (t *Team) GetRepositoriesCtx(ctx context.Context) (err error) {
 	t.Repos, err = GetTeamRepositories(ctx, &SearchTeamRepoOptions{
 		TeamID: t.ID,
 	})
-	return
+	return err
 }
 
 // GetMembersCtx returns paginated members in team of organization.
@@ -346,3 +351,9 @@ func GetRepoTeams(ctx context.Context, repo *repo_model.Repository) (teams []*Te
 		OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END").
 		Find(&teams)
 }
+
+// IncrTeamRepoNum increases the number of repos for the given team by 1
+func IncrTeamRepoNum(ctx context.Context, teamID int64) error {
+	_, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team))
+	return err
+}
diff --git a/models/organization/team_invite.go b/models/organization/team_invite.go
new file mode 100644
index 0000000000000..4504a2e9fef30
--- /dev/null
+++ b/models/organization/team_invite.go
@@ -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
+}
diff --git a/models/organization/team_invite_test.go b/models/organization/team_invite_test.go
new file mode 100644
index 0000000000000..e0596ec28da57
--- /dev/null
+++ b/models/organization/team_invite_test.go
@@ -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)
+	})
+}
diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go
index 717d754c40b7f..3ac4fa926b839 100644
--- a/models/organization/team_repo.go
+++ b/models/organization/team_repo.go
@@ -55,7 +55,7 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) ([]*r
 		Find(&repos)
 }
 
-// AddTeamRepo addes a repo for an organization's team
+// AddTeamRepo adds a repo for an organization's team
 func AddTeamRepo(ctx context.Context, orgID, teamID, repoID int64) error {
 	_, err := db.GetEngine(ctx).Insert(&TeamRepo{
 		OrgID:  orgID,
@@ -81,5 +81,6 @@ func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode per
 		Join("INNER", "team_repo", "team_repo.team_id = team.id").
 		And("team_repo.org_id = ?", orgID).
 		And("team_repo.repo_id = ?", repoID).
+		OrderBy("name").
 		Find(&teams)
 }
diff --git a/models/organization/team_test.go b/models/organization/team_test.go
index 829c440c2938f..c8d58a0eb7a8c 100644
--- a/models/organization/team_test.go
+++ b/models/organization/team_test.go
@@ -17,22 +17,22 @@ import (
 func TestTeam_IsOwnerTeam(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
 	assert.True(t, team.IsOwnerTeam())
 
-	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team)
+	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
 	assert.False(t, team.IsOwnerTeam())
 }
 
 func TestTeam_IsMember(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}).(*organization.Team)
+	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
 	assert.True(t, team.IsMember(2))
 	assert.False(t, team.IsMember(4))
 	assert.False(t, team.IsMember(unittest.NonexistentID))
 
-	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}).(*organization.Team)
+	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
 	assert.True(t, team.IsMember(2))
 	assert.True(t, team.IsMember(4))
 	assert.False(t, team.IsMember(unittest.NonexistentID))
@@ -42,7 +42,7 @@ func TestTeam_GetRepositories(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, team.GetRepositoriesCtx(db.DefaultContext))
 		assert.Len(t, team.Repos, team.NumRepos)
 		for _, repo := range team.Repos {
@@ -57,7 +57,7 @@ func TestTeam_GetMembers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.NoError(t, team.GetMembersCtx(db.DefaultContext))
 		assert.Len(t, team.Members, team.NumMembers)
 		for _, member := range team.Members {
@@ -126,7 +126,7 @@ func TestGetTeamMembers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID int64) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		members, err := organization.GetTeamMembers(db.DefaultContext, &organization.SearchMembersOptions{
 			TeamID: teamID,
 		})
@@ -173,7 +173,7 @@ func TestHasTeamRepo(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(teamID, repoID int64, expected bool) {
-		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}).(*organization.Team)
+		team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
 		assert.Equal(t, expected, organization.HasTeamRepo(db.DefaultContext, team.OrgID, teamID, repoID))
 	}
 	test(1, 1, false)
diff --git a/models/packages/conan/search.go b/models/packages/conan/search.go
index 6a2cfa38f5958..39a90004597d1 100644
--- a/models/packages/conan/search.go
+++ b/models/packages/conan/search.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/packages"
+	"code.gitea.io/gitea/modules/container"
 	conan_module "code.gitea.io/gitea/modules/packages/conan"
 
 	"xorm.io/builder"
@@ -88,7 +89,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
 		return nil, err
 	}
 
-	unique := make(map[string]bool)
+	unique := make(container.Set[string])
 	for _, info := range results {
 		recipe := fmt.Sprintf("%s/%s", info.Name, info.Version)
 
@@ -111,7 +112,7 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er
 			}
 		}
 
-		unique[recipe] = true
+		unique.Add(recipe)
 	}
 
 	recipes := make([]string, 0, len(unique))
diff --git a/models/packages/container/search.go b/models/packages/container/search.go
index 972cac9528f86..e4a5a538488fd 100644
--- a/models/packages/container/search.go
+++ b/models/packages/container/search.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/packages"
+	user_model "code.gitea.io/gitea/models/user"
 	container_module "code.gitea.io/gitea/modules/packages/container"
 
 	"xorm.io/builder"
@@ -164,6 +165,7 @@ type ImageTagsSearchOptions struct {
 	PackageID int64
 	Query     string
 	IsTagged  bool
+	Sort      packages.VersionSort
 	db.Paginator
 }
 
@@ -194,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)
@@ -210,6 +226,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
 	return pvs, count, err
 }
 
+// SearchExpiredUploadedBlobs gets all uploaded blobs which are older than specified
 func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
 	var cond builder.Cond = builder.Eq{
 		"package_version.is_internal":   true,
@@ -225,3 +242,37 @@ func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([
 		Where(cond).
 		Find(&pfs)
 }
+
+// GetRepositories gets a sorted list of all repositories
+func GetRepositories(ctx context.Context, actor *user_model.User, n int, last string) ([]string, error) {
+	var cond builder.Cond = builder.Eq{
+		"package.type":              packages.TypeContainer,
+		"package_property.ref_type": packages.PropertyTypePackage,
+		"package_property.name":     container_module.PropertyRepository,
+	}
+
+	cond = cond.And(builder.Exists(
+		builder.
+			Select("package_version.id").
+			Where(builder.Eq{"package_version.is_internal": false}.And(builder.Expr("package.id = package_version.package_id"))).
+			From("package_version"),
+	))
+
+	if last != "" {
+		cond = cond.And(builder.Gt{"package_property.value": strings.ToLower(last)})
+	}
+
+	cond = cond.And(user_model.BuildCanSeeUserCondition(actor))
+
+	sess := db.GetEngine(ctx).
+		Table("package").
+		Select("package_property.value").
+		Join("INNER", "user", "`user`.id = package.owner_id").
+		Join("INNER", "package_property", "package_property.ref_id = package.id").
+		Where(cond).
+		Asc("package_property.value").
+		Limit(n)
+
+	repositories := make([]string, 0, n)
+	return repositories, sess.Find(&repositories)
+}
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index fbdc40f37fbb2..357574a706c2d 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -19,8 +19,10 @@ import (
 	"code.gitea.io/gitea/modules/packages/maven"
 	"code.gitea.io/gitea/modules/packages/npm"
 	"code.gitea.io/gitea/modules/packages/nuget"
+	"code.gitea.io/gitea/modules/packages/pub"
 	"code.gitea.io/gitea/modules/packages/pypi"
 	"code.gitea.io/gitea/modules/packages/rubygems"
+	"code.gitea.io/gitea/modules/packages/vagrant"
 
 	"github.com/hashicorp/go-version"
 )
@@ -40,15 +42,16 @@ func (l PackagePropertyList) GetByName(name string) string {
 
 // PackageDescriptor describes a package
 type PackageDescriptor struct {
-	Package    *Package
-	Owner      *user_model.User
-	Repository *repo_model.Repository
-	Version    *PackageVersion
-	SemVer     *version.Version
-	Creator    *user_model.User
-	Properties PackagePropertyList
-	Metadata   interface{}
-	Files      []*PackageFileDescriptor
+	Package           *Package
+	Owner             *user_model.User
+	Repository        *repo_model.Repository
+	Version           *PackageVersion
+	SemVer            *version.Version
+	Creator           *user_model.User
+	PackageProperties PackagePropertyList
+	VersionProperties PackagePropertyList
+	Metadata          interface{}
+	Files             []*PackageFileDescriptor
 }
 
 // PackageFileDescriptor describes a package file
@@ -102,6 +105,10 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
 			return nil, err
 		}
 	}
+	pps, err := GetProperties(ctx, PropertyTypePackage, p.ID)
+	if err != nil {
+		return nil, err
+	}
 	pvps, err := GetProperties(ctx, PropertyTypeVersion, pv.ID)
 	if err != nil {
 		return nil, err
@@ -138,10 +145,14 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
 		metadata = &npm.Metadata{}
 	case TypeMaven:
 		metadata = &maven.Metadata{}
+	case TypePub:
+		metadata = &pub.Metadata{}
 	case TypePyPI:
 		metadata = &pypi.Metadata{}
 	case TypeRubyGems:
 		metadata = &rubygems.Metadata{}
+	case TypeVagrant:
+		metadata = &vagrant.Metadata{}
 	default:
 		panic(fmt.Sprintf("unknown package type: %s", string(p.Type)))
 	}
@@ -152,15 +163,16 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
 	}
 
 	return &PackageDescriptor{
-		Package:    p,
-		Owner:      o,
-		Repository: repository,
-		Version:    pv,
-		SemVer:     semVer,
-		Creator:    creator,
-		Properties: PackagePropertyList(pvps),
-		Metadata:   metadata,
-		Files:      pfds,
+		Package:           p,
+		Owner:             o,
+		Repository:        repository,
+		Version:           pv,
+		SemVer:            semVer,
+		Creator:           creator,
+		PackageProperties: PackagePropertyList(pps),
+		VersionProperties: PackagePropertyList(pvps),
+		Metadata:          metadata,
+		Files:             pfds,
 	}, nil
 }
 
diff --git a/models/packages/package.go b/models/packages/package.go
index bdb535492bb40..e39a7c4e411d4 100644
--- a/models/packages/package.go
+++ b/models/packages/package.go
@@ -39,8 +39,10 @@ const (
 	TypeMaven     Type = "maven"
 	TypeNpm       Type = "npm"
 	TypeNuGet     Type = "nuget"
+	TypePub       Type = "pub"
 	TypePyPI      Type = "pypi"
 	TypeRubyGems  Type = "rubygems"
+	TypeVagrant   Type = "vagrant"
 )
 
 // Name gets the name of the package type
@@ -62,10 +64,14 @@ func (pt Type) Name() string {
 		return "npm"
 	case TypeNuGet:
 		return "NuGet"
+	case TypePub:
+		return "Pub"
 	case TypePyPI:
 		return "PyPI"
 	case TypeRubyGems:
 		return "RubyGems"
+	case TypeVagrant:
+		return "Vagrant"
 	}
 	panic(fmt.Sprintf("unknown package type: %s", string(pt)))
 }
@@ -89,10 +95,14 @@ func (pt Type) SVGName() string {
 		return "gitea-npm"
 	case TypeNuGet:
 		return "gitea-nuget"
+	case TypePub:
+		return "gitea-pub"
 	case TypePyPI:
 		return "gitea-python"
 	case TypeRubyGems:
 		return "gitea-rubygems"
+	case TypeVagrant:
+		return "gitea-vagrant"
 	}
 	panic(fmt.Sprintf("unknown package type: %s", string(pt)))
 }
@@ -131,6 +141,12 @@ func TryInsertPackage(ctx context.Context, p *Package) (*Package, error) {
 	return p, nil
 }
 
+// DeletePackageByID deletes a package by id
+func DeletePackageByID(ctx context.Context, packageID int64) error {
+	_, err := db.GetEngine(ctx).ID(packageID).Delete(&Package{})
+	return err
+}
+
 // SetRepositoryLink sets the linked repository
 func SetRepositoryLink(ctx context.Context, packageID, repoID int64) error {
 	_, err := db.GetEngine(ctx).ID(packageID).Cols("repo_id").Update(&Package{RepoID: repoID})
@@ -192,26 +208,32 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]
 		Find(&ps)
 }
 
-// DeletePackagesIfUnreferenced deletes a package if there are no associated versions
-func DeletePackagesIfUnreferenced(ctx context.Context) error {
+// FindUnreferencedPackages gets all packages without associated versions
+func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) {
 	in := builder.
 		Select("package.id").
 		From("package").
 		LeftJoin("package_version", "package_version.package_id = package.id").
 		Where(builder.Expr("package_version.id IS NULL"))
 
-	_, err := db.GetEngine(ctx).
+	ps := make([]*Package, 0, 10)
+	return ps, db.GetEngine(ctx).
 		// double select workaround for MySQL
 		// https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition
 		Where(builder.In("package.id", builder.Select("id").From(in, "temp"))).
-		Delete(&Package{})
-
-	return err
+		Find(&ps)
 }
 
-// HasOwnerPackages tests if a user/org has packages
+// HasOwnerPackages tests if a user/org has accessible packages
 func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) {
-	return db.GetEngine(ctx).Where("owner_id = ?", ownerID).Exist(&Package{})
+	return db.GetEngine(ctx).
+		Table("package_version").
+		Join("INNER", "package", "package.id = package_version.package_id").
+		Where(builder.Eq{
+			"package_version.is_internal": false,
+			"package.owner_id":            ownerID,
+		}).
+		Exist(&PackageVersion{})
 }
 
 // HasRepositoryPackages tests if a repository has packages
diff --git a/models/packages/package_property.go b/models/packages/package_property.go
index bf7dc346c6c97..fc10713801947 100644
--- a/models/packages/package_property.go
+++ b/models/packages/package_property.go
@@ -21,9 +21,11 @@ const (
 	PropertyTypeVersion PropertyType = iota // 0
 	// PropertyTypeFile means the reference is a package file
 	PropertyTypeFile // 1
+	// PropertyTypePackage means the reference is a package
+	PropertyTypePackage // 2
 )
 
-// PackageProperty represents a property of a package version or file
+// PackageProperty represents a property of a package, version or file
 type PackageProperty struct {
 	ID      int64        `xorm:"pk autoincr"`
 	RefType PropertyType `xorm:"INDEX NOT NULL"`
@@ -68,3 +70,9 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
 	_, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{})
 	return err
 }
+
+// DeletePropertyByName deletes properties by name
+func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
+	_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
+	return err
+}
diff --git a/models/packages/package_test.go b/models/packages/package_test.go
new file mode 100644
index 0000000000000..915ef15f91f97
--- /dev/null
+++ b/models/packages/package_test.go
@@ -0,0 +1,69 @@
+// 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 packages_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	packages_model "code.gitea.io/gitea/models/packages"
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+
+	_ "code.gitea.io/gitea/models"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", ".."),
+	})
+}
+
+func TestHasOwnerPackages(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+
+	p, err := packages_model.TryInsertPackage(db.DefaultContext, &packages_model.Package{
+		OwnerID:   owner.ID,
+		LowerName: "package",
+	})
+	assert.NotNil(t, p)
+	assert.NoError(t, err)
+
+	// A package without package versions gets automatically cleaned up and should return false
+	has, err := packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
+	assert.False(t, has)
+	assert.NoError(t, err)
+
+	pv, err := packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
+		PackageID:    p.ID,
+		LowerVersion: "internal",
+		IsInternal:   true,
+	})
+	assert.NotNil(t, pv)
+	assert.NoError(t, err)
+
+	// A package with an internal package version gets automatically cleaned up and should return false
+	has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
+	assert.False(t, has)
+	assert.NoError(t, err)
+
+	pv, err = packages_model.GetOrInsertVersion(db.DefaultContext, &packages_model.PackageVersion{
+		PackageID:    p.ID,
+		LowerVersion: "normal",
+		IsInternal:   false,
+	})
+	assert.NotNil(t, pv)
+	assert.NoError(t, err)
+
+	// A package with a normal package version should return true
+	has, err = packages_model.HasOwnerPackages(db.DefaultContext, owner.ID)
+	assert.True(t, has)
+	assert.NoError(t, err)
+}
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index 78e76c50545ee..4b2a6f84c382c 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -107,7 +107,7 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
 			ExactMatch: true,
 			Value:      version,
 		},
-		IsInternal: isInternal,
+		IsInternal: util.OptionalBoolOf(isInternal),
 		Paginator:  db.NewAbsoluteListOptions(0, 1),
 	})
 	if err != nil {
@@ -122,8 +122,9 @@ func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType
 // GetVersionsByPackageType gets all versions of a specific type
 func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) {
 	pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
-		OwnerID: ownerID,
-		Type:    packageType,
+		OwnerID:    ownerID,
+		Type:       packageType,
+		IsInternal: util.OptionalBoolFalse,
 	})
 	return pvs, err
 }
@@ -137,6 +138,7 @@ func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Ty
 			ExactMatch: true,
 			Value:      name,
 		},
+		IsInternal: util.OptionalBoolFalse,
 	})
 	return pvs, err
 }
@@ -161,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 {
@@ -171,15 +184,18 @@ type PackageSearchOptions struct {
 	Name            SearchValue       // only results with the specific name are found
 	Version         SearchValue       // only results with the specific version are found
 	Properties      map[string]string // only results are found which contain all listed version properties with the specific value
-	IsInternal      bool
+	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
 }
 
 func (opts *PackageSearchOptions) toConds() builder.Cond {
-	var cond builder.Cond = builder.Eq{"package_version.is_internal": opts.IsInternal}
+	cond := builder.NewCond()
+	if !opts.IsInternal.IsNone() {
+		cond = builder.Eq{"package_version.is_internal": opts.IsInternal.IsTrue()}
+	}
 
 	if opts.OwnerID != 0 {
 		cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID})
@@ -235,7 +251,7 @@ func (opts *PackageSearchOptions) toConds() builder.Cond {
 	}
 
 	if !opts.HasFiles.IsNone() {
-		var filesCond builder.Cond = builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
+		filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
 
 		if opts.HasFiles.IsFalse() {
 			filesCond = builder.Not{filesCond}
@@ -249,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")
@@ -289,7 +305,7 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
 
 	sess := db.GetEngine(ctx).
 		Table("package_version").
-		Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))").
+		Join("LEFT", "package_version pv2", "package_version.package_id = pv2.package_id AND pv2.is_internal = ? AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))", false).
 		Join("INNER", "package", "package.id = package_version.package_id").
 		Where(cond)
 
diff --git a/models/perm/access/access.go b/models/perm/access/access.go
index 7647519025977..7344e114a64e2 100644
--- a/models/perm/access/access.go
+++ b/models/perm/access/access.go
@@ -86,7 +86,13 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo
 // FIXME: do cross-comparison so reduce deletions and additions to the minimum?
 func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
 	minMode := perm.AccessModeRead
-	if !repo.IsPrivate {
+	if err := repo.GetOwner(ctx); err != nil {
+		return fmt.Errorf("GetOwner: %w", err)
+	}
+
+	// If the repo isn't private and isn't owned by a organization,
+	// increase the minMode to Write.
+	if !repo.IsPrivate && !repo.Owner.IsOrganization() {
 		minMode = perm.AccessModeWrite
 	}
 
@@ -105,14 +111,14 @@ func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap
 
 	// Delete old accesses and insert new ones for repository.
 	if _, err = db.DeleteByBean(ctx, &Access{RepoID: repo.ID}); err != nil {
-		return fmt.Errorf("delete old accesses: %v", err)
+		return fmt.Errorf("delete old accesses: %w", err)
 	}
 	if len(newAccesses) == 0 {
 		return nil
 	}
 
 	if err = db.Insert(ctx, newAccesses); err != nil {
-		return fmt.Errorf("insert new accesses: %v", err)
+		return fmt.Errorf("insert new accesses: %w", err)
 	}
 	return nil
 }
@@ -121,7 +127,7 @@ func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap
 func refreshCollaboratorAccesses(ctx context.Context, repoID int64, accessMap map[int64]*userAccess) error {
 	collaborators, err := repo_model.GetCollaborators(ctx, repoID, db.ListOptions{})
 	if err != nil {
-		return fmt.Errorf("getCollaborations: %v", err)
+		return fmt.Errorf("getCollaborations: %w", err)
 	}
 	for _, c := range collaborators {
 		if c.User.IsGhost() {
@@ -145,7 +151,7 @@ func RecalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
 	}
 
 	if err = refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
-		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
+		return fmt.Errorf("refreshCollaboratorAccesses: %w", err)
 	}
 
 	teams, err := organization.FindOrgTeams(ctx, repo.Owner.ID)
@@ -167,7 +173,7 @@ func RecalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
 		}
 
 		if err = t.GetMembersCtx(ctx); err != nil {
-			return fmt.Errorf("getMembers '%d': %v", t.ID, err)
+			return fmt.Errorf("getMembers '%d': %w", t.ID, err)
 		}
 		for _, m := range t.Members {
 			updateUserAccess(accessMap, m, t.AccessMode)
@@ -218,10 +224,10 @@ func RecalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
 
 	// Delete old user accesses and insert new one for repository.
 	if _, err = e.Delete(&Access{RepoID: repo.ID, UserID: uid}); err != nil {
-		return fmt.Errorf("delete old user accesses: %v", err)
+		return fmt.Errorf("delete old user accesses: %w", err)
 	} else if accessMode >= minMode {
 		if err = db.Insert(ctx, &Access{RepoID: repo.ID, UserID: uid, Mode: accessMode}); err != nil {
-			return fmt.Errorf("insert new user accesses: %v", err)
+			return fmt.Errorf("insert new user accesses: %w", err)
 		}
 	}
 	return nil
@@ -235,7 +241,7 @@ func RecalculateAccesses(ctx context.Context, repo *repo_model.Repository) error
 
 	accessMap := make(map[int64]*userAccess, 20)
 	if err := refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
-		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
+		return fmt.Errorf("refreshCollaboratorAccesses: %w", err)
 	}
 	return refreshAccesses(ctx, repo, accessMap)
 }
diff --git a/models/perm/access/access_test.go b/models/perm/access/access_test.go
index a5e0448d3db86..7f58be4f393b5 100644
--- a/models/perm/access/access_test.go
+++ b/models/perm/access/access_test.go
@@ -7,13 +7,10 @@ package access_test
 import (
 	"testing"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/organization"
 	perm_model "code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 
@@ -23,21 +20,21 @@ import (
 func TestAccessLevel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User)
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29})
 	// A public repository owned by User 2
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.False(t, repo1.IsPrivate)
 	// A private repository owned by Org 3
-	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 	assert.True(t, repo3.IsPrivate)
 
 	// Another public repository
-	repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
+	repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
 	assert.False(t, repo4.IsPrivate)
 	// org. owned private repo
-	repo24 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}).(*repo_model.Repository)
+	repo24 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
 
 	level, err := access_model.AccessLevel(user2, repo1)
 	assert.NoError(t, err)
@@ -74,13 +71,13 @@ func TestAccessLevel(t *testing.T) {
 func TestHasAccess(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
 	// A public repository owned by User 2
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.False(t, repo1.IsPrivate)
 	// A private repository owned by Org 3
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 	assert.True(t, repo2.IsPrivate)
 
 	has, err := access_model.HasAccess(db.DefaultContext, user1.ID, repo1)
@@ -100,7 +97,7 @@ func TestHasAccess(t *testing.T) {
 func TestRepository_RecalculateAccesses(t *testing.T) {
 	// test with organization repo
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 	assert.NoError(t, repo1.GetOwner(db.DefaultContext))
 
 	_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 2, RepoID: 3})
@@ -117,7 +114,7 @@ func TestRepository_RecalculateAccesses(t *testing.T) {
 func TestRepository_RecalculateAccesses2(t *testing.T) {
 	// test with non-organization repo
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
 	assert.NoError(t, repo1.GetOwner(db.DefaultContext))
 
 	_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 4, RepoID: 4})
@@ -128,249 +125,3 @@ func TestRepository_RecalculateAccesses2(t *testing.T) {
 	assert.NoError(t, err)
 	assert.False(t, has)
 }
-
-func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	// public non-organization repo
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
-	assert.NoError(t, repo.LoadUnits(db.DefaultContext))
-
-	// plain user
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// change to collaborator
-	assert.NoError(t, models.AddCollaborator(repo, user))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// collaborator
-	collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, collaborator)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// owner
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// admin
-	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-}
-
-func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	// private non-organization repo
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
-	assert.NoError(t, repo.LoadUnits(db.DefaultContext))
-
-	// plain user
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.False(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// change to collaborator to default write access
-	assert.NoError(t, models.AddCollaborator(repo, user))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// owner
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// admin
-	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-}
-
-func TestRepoPermissionPublicOrgRepo(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	// public organization repo
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}).(*repo_model.Repository)
-	assert.NoError(t, repo.LoadUnits(db.DefaultContext))
-
-	// plain user
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// change to collaborator to default write access
-	assert.NoError(t, models.AddCollaborator(repo, user))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// org member team owner
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// org member team tester
-	member := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, member)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-	}
-	assert.True(t, perm.CanWrite(unit.TypeIssues))
-	assert.False(t, perm.CanWrite(unit.TypeCode))
-
-	// admin
-	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-}
-
-func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	// private organization repo
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}).(*repo_model.Repository)
-	assert.NoError(t, repo.LoadUnits(db.DefaultContext))
-
-	// plain user
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.False(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// change to collaborator to default write access
-	assert.NoError(t, models.AddCollaborator(repo, user))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.False(t, perm.CanWrite(unit.Type))
-	}
-
-	// org member team owner
-	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// update team information and then check permission
-	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team)
-	err = organization.UpdateTeamUnits(team, nil)
-	assert.NoError(t, err)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-
-	// org member team tester
-	tester := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, tester)
-	assert.NoError(t, err)
-	assert.True(t, perm.CanWrite(unit.TypeIssues))
-	assert.False(t, perm.CanWrite(unit.TypeCode))
-	assert.False(t, perm.CanRead(unit.TypeCode))
-
-	// org member team reviewer
-	reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, reviewer)
-	assert.NoError(t, err)
-	assert.False(t, perm.CanRead(unit.TypeIssues))
-	assert.False(t, perm.CanWrite(unit.TypeCode))
-	assert.True(t, perm.CanRead(unit.TypeCode))
-
-	// admin
-	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
-	assert.NoError(t, err)
-	for _, unit := range repo.Units {
-		assert.True(t, perm.CanRead(unit.Type))
-		assert.True(t, perm.CanWrite(unit.Type))
-	}
-}
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 6bc1c82703a7e..93e3bdd6d8468 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -273,7 +273,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
 		}
 	}
 
-	return
+	return perm, err
 }
 
 // IsUserRealRepoAdmin check if this user is real repo admin
@@ -430,3 +430,17 @@ func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64
 	}
 	return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
 }
+
+// CheckRepoUnitUser check whether user could visit the unit of this repository
+func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
+	if user != nil && user.IsAdmin {
+		return true
+	}
+	perm, err := GetUserRepoPermission(ctx, repo, user)
+	if err != nil {
+		log.Error("GetUserRepoPermission: %w", err)
+		return false
+	}
+
+	return perm.CanRead(unitType)
+}
diff --git a/models/project/issue.go b/models/project/issue.go
index 6e6a8c574666f..59af7063a546f 100644
--- a/models/project/issue.go
+++ b/models/project/issue.go
@@ -103,7 +103,7 @@ func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) erro
 	})
 }
 
-func (pb *Board) removeIssues(ctx context.Context) error {
-	_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID)
+func (b *Board) removeIssues(ctx context.Context) error {
+	_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
 	return err
 }
diff --git a/models/project/project.go b/models/project/project.go
index 0aa37cc5c9071..ccdf5342d4ff1 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -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"`
@@ -139,7 +147,7 @@ func GetProjects(ctx context.Context, opts SearchOptions) ([]*Project, int64, er
 
 	count, err := e.Where(cond).Count(new(Project))
 	if err != nil {
-		return nil, 0, fmt.Errorf("Count: %v", err)
+		return nil, 0, fmt.Errorf("Count: %w", err)
 	}
 
 	e = e.Where(cond)
@@ -330,3 +338,40 @@ func DeleteProjectByIDCtx(ctx context.Context, id int64) error {
 
 	return updateRepositoryProjectCount(ctx, p.RepoID)
 }
+
+func DeleteProjectByRepoIDCtx(ctx context.Context, repoID int64) error {
+	switch {
+	case setting.Database.UseSQLite3:
+		if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue WHERE project_issue.id IN (SELECT project_issue.id FROM project_issue INNER JOIN project WHERE project.id = project_issue.project_id AND project.repo_id = ?)", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_board WHERE project_board.id IN (SELECT project_board.id FROM project_board INNER JOIN project WHERE project.id = project_board.project_id AND project.repo_id = ?)", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil {
+			return err
+		}
+	case setting.Database.UsePostgreSQL:
+		if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_issue USING project WHERE project.id = project_issue.project_id AND project.repo_id = ? ", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Exec("DELETE FROM project_board USING project WHERE project.id = project_board.project_id AND project.repo_id = ? ", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil {
+			return err
+		}
+	default:
+		if _, err := db.GetEngine(ctx).Exec("DELETE project_issue FROM project_issue INNER JOIN project ON project.id = project_issue.project_id WHERE project.repo_id = ? ", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Exec("DELETE project_board FROM project_board INNER JOIN project ON project.id = project_board.project_id WHERE project.repo_id = ? ", repoID); err != nil {
+			return err
+		}
+		if _, err := db.GetEngine(ctx).Table("project").Where("repo_id = ? ", repoID).Delete(&Project{}); err != nil {
+			return err
+		}
+	}
+
+	return updateRepositoryProjectCount(ctx, repoID)
+}
diff --git a/models/pull/automerge.go b/models/pull/automerge.go
index d0aca2e85f976..16ab5af09391b 100644
--- a/models/pull/automerge.go
+++ b/models/pull/automerge.go
@@ -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{})
diff --git a/models/repo.go b/models/repo.go
index e9d83f5f32720..26fe51b5a6e78 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -12,188 +12,34 @@ import (
 
 	_ "image/jpeg" // Needed for jpeg support
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	admin_model "code.gitea.io/gitea/models/admin"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	git_model "code.gitea.io/gitea/models/git"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
-	"code.gitea.io/gitea/models/perm"
 	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"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
-	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
 
-// NewRepoContext creates a new repository context
-func NewRepoContext() {
-	unit.LoadUnitConfig()
-}
-
-// CheckRepoUnitUser check whether user could visit the unit of this repository
-func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
-	if user != nil && user.IsAdmin {
-		return true
-	}
-	perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
-	if err != nil {
-		log.Error("GetUserRepoPermission(): %v", err)
-		return false
-	}
-
-	return perm.CanRead(unitType)
-}
-
-// CreateRepoOptions contains the create repository options
-type CreateRepoOptions struct {
-	Name           string
-	Description    string
-	OriginalURL    string
-	GitServiceType api.GitServiceType
-	Gitignores     string
-	IssueLabels    string
-	License        string
-	Readme         string
-	DefaultBranch  string
-	IsPrivate      bool
-	IsMirror       bool
-	IsTemplate     bool
-	AutoInit       bool
-	Status         repo_model.RepositoryStatus
-	TrustModel     repo_model.TrustModelType
-	MirrorInterval string
-}
-
-// CreateRepository creates a repository for the user/organization.
-func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt bool) (err error) {
-	if err = repo_model.IsUsableRepoName(repo.Name); err != nil {
-		return err
-	}
-
-	has, err := repo_model.IsRepositoryExist(ctx, u, repo.Name)
-	if err != nil {
-		return fmt.Errorf("IsRepositoryExist: %v", err)
-	} else if has {
-		return repo_model.ErrRepoAlreadyExist{
-			Uname: u.Name,
-			Name:  repo.Name,
-		}
-	}
-
-	repoPath := repo_model.RepoPath(u.Name, repo.Name)
-	isExist, err := util.IsExist(repoPath)
-	if err != nil {
-		log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
-		return err
-	}
-	if !overwriteOrAdopt && isExist {
-		log.Error("Files already exist in %s and we are not going to adopt or delete.", repoPath)
-		return repo_model.ErrRepoFilesAlreadyExist{
-			Uname: u.Name,
-			Name:  repo.Name,
-		}
-	}
-
-	if err = db.Insert(ctx, repo); err != nil {
-		return err
-	}
-	if err = repo_model.DeleteRedirect(ctx, u.ID, repo.Name); err != nil {
-		return err
-	}
-
-	// insert units for repo
-	units := make([]repo_model.RepoUnit, 0, len(unit.DefaultRepoUnits))
-	for _, tp := range unit.DefaultRepoUnits {
-		if tp == unit.TypeIssues {
-			units = append(units, repo_model.RepoUnit{
-				RepoID: repo.ID,
-				Type:   tp,
-				Config: &repo_model.IssuesConfig{
-					EnableTimetracker:                setting.Service.DefaultEnableTimetracking,
-					AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
-					EnableDependencies:               setting.Service.DefaultEnableDependencies,
-				},
-			})
-		} else if tp == unit.TypePullRequests {
-			units = append(units, repo_model.RepoUnit{
-				RepoID: repo.ID,
-				Type:   tp,
-				Config: &repo_model.PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), AllowRebaseUpdate: true},
-			})
-		} else {
-			units = append(units, repo_model.RepoUnit{
-				RepoID: repo.ID,
-				Type:   tp,
-			})
-		}
-	}
-
-	if err = db.Insert(ctx, units); err != nil {
-		return err
-	}
-
-	// Remember visibility preference.
-	u.LastRepoVisibility = repo.IsPrivate
-	if err = user_model.UpdateUserCols(ctx, u, "last_repo_visibility"); err != nil {
-		return fmt.Errorf("updateUser: %v", err)
-	}
-
-	if _, err = db.GetEngine(ctx).Incr("num_repos").ID(u.ID).Update(new(user_model.User)); err != nil {
-		return fmt.Errorf("increment user total_repos: %v", err)
-	}
-	u.NumRepos++
-
-	// Give access to all members in teams with access to all repositories.
-	if u.IsOrganization() {
-		teams, err := organization.FindOrgTeams(ctx, u.ID)
-		if err != nil {
-			return fmt.Errorf("loadTeams: %v", err)
-		}
-		for _, t := range teams {
-			if t.IncludesAllRepositories {
-				if err := addRepository(ctx, t, repo); err != nil {
-					return fmt.Errorf("addRepository: %v", err)
-				}
-			}
-		}
+// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
+var ItemsPerPage = 40
 
-		if isAdmin, err := access_model.IsUserRepoAdmin(ctx, repo, doer); err != nil {
-			return fmt.Errorf("IsUserRepoAdminCtx: %v", err)
-		} else if !isAdmin {
-			// Make creator repo admin if it wasn't assigned automatically
-			if err = addCollaborator(ctx, repo, doer); err != nil {
-				return fmt.Errorf("AddCollaborator: %v", err)
-			}
-			if err = repo_model.ChangeCollaborationAccessModeCtx(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
-				return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
-			}
-		}
-	} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
-		// Organization automatically called this in addRepository method.
-		return fmt.Errorf("recalculateAccesses: %v", err)
-	}
-
-	if setting.Service.AutoWatchNewRepos {
-		if err = repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil {
-			return fmt.Errorf("watchRepo: %v", err)
-		}
-	}
-
-	if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil {
-		return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err)
-	}
-
-	return nil
+// Init initialize model
+func Init() error {
+	unit.LoadUnitConfig()
+	return system_model.Init()
 }
 
 // DeleteRepository deletes a repository for a user or organization.
@@ -228,12 +74,12 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 	// Delete Deploy Keys
 	deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID})
 	if err != nil {
-		return fmt.Errorf("listDeployKeys: %v", err)
+		return fmt.Errorf("listDeployKeys: %w", err)
 	}
 	needRewriteKeysFile := len(deployKeys) > 0
 	for _, dKey := range deployKeys {
 		if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil {
-			return fmt.Errorf("deleteDeployKeys: %v", err)
+			return fmt.Errorf("deleteDeployKeys: %w", err)
 		}
 	}
 
@@ -277,32 +123,36 @@ 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},
-		&Action{RepoID: repo.ID},
+		&activities_model.Action{RepoID: repo.ID},
 		&repo_model.Collaboration{RepoID: repoID},
 		&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},
 		&repo_model.Mirror{RepoID: repoID},
-		&Notification{RepoID: repoID},
+		&activities_model.Notification{RepoID: repoID},
 		&git_model.ProtectedBranch{RepoID: repoID},
 		&git_model.ProtectedTag{RepoID: repoID},
 		&repo_model.PushMirror{RepoID: repoID},
-		&Release{RepoID: repoID},
+		&repo_model.Release{RepoID: repoID},
 		&repo_model.RepoIndexerStatus{RepoID: repoID},
 		&repo_model.Redirect{RedirectRepoID: repoID},
 		&repo_model.RepoUnit{RepoID: repoID},
 		&repo_model.Star{RepoID: repoID},
-		&Task{RepoID: repoID},
+		&admin_model.Task{RepoID: repoID},
 		&repo_model.Watch{RepoID: repoID},
 		&webhook.Webhook{RepoID: repoID},
 	); err != nil {
-		return fmt.Errorf("deleteBeans: %v", err)
+		return fmt.Errorf("deleteBeans: %w", err)
 	}
 
 	// Delete Labels and related objects
@@ -322,13 +172,13 @@ 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
 	}
 
 	if repo.IsFork {
 		if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
-			return fmt.Errorf("decrease fork count: %v", err)
+			return fmt.Errorf("decrease fork count: %w", err)
 		}
 	}
 
@@ -342,16 +192,8 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 		}
 	}
 
-	projects, _, err := project_model.GetProjects(ctx, project_model.SearchOptions{
-		RepoID: repoID,
-	})
-	if err != nil {
-		return fmt.Errorf("get projects: %v", err)
-	}
-	for i := range projects {
-		if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil {
-			return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
-		}
+	if err := project_model.DeleteProjectByRepoIDCtx(ctx, repoID); err != nil {
+		return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err)
 	}
 
 	// Remove LFS objects
@@ -385,8 +227,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 
 	archivePaths := make([]string, 0, len(archives))
 	for _, v := range archives {
-		p, _ := v.RelativePath()
-		archivePaths = append(archivePaths, p)
+		archivePaths = append(archivePaths, v.RelativePath())
 	}
 
 	if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil {
@@ -435,41 +276,41 @@ 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 {
 		if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
-			return fmt.Errorf("Failed to remove %s: %v", repo.Avatar, err)
+			return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err)
 		}
 	}
 
@@ -563,24 +404,19 @@ func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
 }
 
 func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
-	return repoStatsCorrectNum(ctx, id, false, "num_issues")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, false, false)
 }
 
 func repoStatsCorrectNumPulls(ctx context.Context, id int64) error {
-	return repoStatsCorrectNum(ctx, id, true, "num_pulls")
-}
-
-func repoStatsCorrectNum(ctx context.Context, id int64, isPull bool, field string) error {
-	_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_pull=?) WHERE id=?", id, isPull, id)
-	return err
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, false)
 }
 
 func repoStatsCorrectNumClosedIssues(ctx context.Context, id int64) error {
-	return repo_model.StatsCorrectNumClosed(ctx, id, false, "num_closed_issues")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, false, true)
 }
 
 func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
-	return repo_model.StatsCorrectNumClosed(ctx, id, true, "num_closed_pulls")
+	return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
 }
 
 func statsQuery(args ...interface{}) func(context.Context) ([]map[string][]byte, error) {
@@ -606,15 +442,27 @@ func CheckRepoStats(ctx context.Context) error {
 			repoStatsCorrectNumStars,
 			"repository count 'num_stars'",
 		},
+		// Repository.NumIssues
+		{
+			statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", false),
+			repoStatsCorrectNumIssues,
+			"repository count 'num_issues'",
+		},
 		// Repository.NumClosedIssues
 		{
 			statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, false),
 			repoStatsCorrectNumClosedIssues,
 			"repository count 'num_closed_issues'",
 		},
+		// Repository.NumPulls
+		{
+			statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_pull=?)", true),
+			repoStatsCorrectNumPulls,
+			"repository count 'num_pulls'",
+		},
 		// Repository.NumClosedPulls
 		{
-			statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
+			statsQuery("SELECT repo.id FROM `repository` repo WHERE repo.num_closed_pulls!=(SELECT COUNT(*) FROM `issue` WHERE repo_id=repo.id AND is_closed=? AND is_pull=?)", true, true),
 			repoStatsCorrectNumClosedPulls,
 			"repository count 'num_closed_pulls'",
 		},
@@ -756,7 +604,7 @@ func DoctorUserStarNum() (err error) {
 
 	log.Debug("recalculate Stars number for all user finished")
 
-	return
+	return err
 }
 
 // DeleteDeployKey delete deploy keys
@@ -766,18 +614,18 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
 		if asymkey_model.IsErrDeployKeyNotExist(err) {
 			return nil
 		}
-		return fmt.Errorf("GetDeployKeyByID: %v", err)
+		return fmt.Errorf("GetDeployKeyByID: %w", err)
 	}
 
 	// Check if user has access to delete this key.
 	if !doer.IsAdmin {
 		repo, err := repo_model.GetRepositoryByIDCtx(ctx, key.RepoID)
 		if err != nil {
-			return fmt.Errorf("GetRepositoryByID: %v", err)
+			return fmt.Errorf("GetRepositoryByID: %w", err)
 		}
 		has, err := access_model.IsUserRepoAdmin(ctx, repo, doer)
 		if err != nil {
-			return fmt.Errorf("GetUserRepoPermission: %v", err)
+			return fmt.Errorf("GetUserRepoPermission: %w", err)
 		} else if !has {
 			return asymkey_model.ErrKeyAccessDenied{
 				UserID: doer.ID,
@@ -790,7 +638,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
 	if _, err := db.DeleteByBean(ctx, &asymkey_model.DeployKey{
 		ID: key.ID,
 	}); err != nil {
-		return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
+		return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err)
 	}
 
 	// Check if this is the last reference to same key content.
diff --git a/models/repo/archiver.go b/models/repo/archiver.go
index dc64cce49ba9d..003911943f52a 100644
--- a/models/repo/archiver.go
+++ b/models/repo/archiver.go
@@ -39,9 +39,9 @@ func init() {
 	db.RegisterModel(new(RepoArchiver))
 }
 
-// RelativePath returns relative path
-func (archiver *RepoArchiver) RelativePath() (string, error) {
-	return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String()), nil
+// RelativePath returns the archive path relative to the archive storage root.
+func (archiver *RepoArchiver) RelativePath() string {
+	return fmt.Sprintf("%d/%s/%s.%s", archiver.RepoID, archiver.CommitID[:2], archiver.CommitID, archiver.Type.String())
 }
 
 var delRepoArchiver = new(RepoArchiver)
@@ -112,5 +112,5 @@ func FindRepoArchives(opts FindRepoArchiversOption) ([]*RepoArchiver, error) {
 func SetArchiveRepoState(repo *Repository, isArchived bool) (err error) {
 	repo.IsArchived = isArchived
 	_, err = db.GetEngine(db.DefaultContext).Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo)
-	return
+	return err
 }
diff --git a/models/repo/attachment.go b/models/repo/attachment.go
index ddddac2c3dcf0..180d7730ba715 100644
--- a/models/repo/attachment.go
+++ b/models/repo/attachment.go
@@ -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.
@@ -39,7 +40,7 @@ func init() {
 func (a *Attachment) IncreaseDownloadCount() error {
 	// Update download count.
 	if _, err := db.GetEngine(db.DefaultContext).Exec("UPDATE `attachment` SET download_count=download_count+1 WHERE id=?", a.ID); err != nil {
-		return fmt.Errorf("increase attachment count: %v", err)
+		return fmt.Errorf("increase attachment count: %w", err)
 	}
 
 	return nil
@@ -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{}
@@ -226,28 +231,6 @@ func DeleteAttachmentsByRelease(releaseID int64) error {
 	return err
 }
 
-// IterateAttachment iterates attachments; it should not be used when Gitea is servicing users.
-func IterateAttachment(f func(attach *Attachment) error) error {
-	var start int
-	const batchSize = 100
-	for {
-		attachments := make([]*Attachment, 0, batchSize)
-		if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&attachments); err != nil {
-			return err
-		}
-		if len(attachments) == 0 {
-			return nil
-		}
-		start += len(attachments)
-
-		for _, attach := range attachments {
-			if err := f(attach); err != nil {
-				return err
-			}
-		}
-	}
-}
-
 // CountOrphanedAttachments returns the number of bad attachments
 func CountOrphanedAttachments() (int64, error) {
 	return db.GetEngine(db.DefaultContext).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
diff --git a/models/repo/avatar.go b/models/repo/avatar.go
index cdf85bf1ac1ae..1bc37598feef9 100644
--- a/models/repo/avatar.go
+++ b/models/repo/avatar.go
@@ -36,7 +36,7 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error {
 	seed := idToString
 	img, err := avatar.RandomImage([]byte(seed))
 	if err != nil {
-		return fmt.Errorf("RandomImage: %v", err)
+		return fmt.Errorf("RandomImage: %w", err)
 	}
 
 	repo.Avatar = idToString
@@ -47,7 +47,7 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error {
 		}
 		return err
 	}); err != nil {
-		return fmt.Errorf("Failed to create dir %s: %v", repo.CustomAvatarRelativePath(), err)
+		return fmt.Errorf("Failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err)
 	}
 
 	log.Info("New random avatar created for repository: %d", repo.ID)
diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go
index be05eba74c1e8..0aaa749210b2f 100644
--- a/models/repo/collaboration.go
+++ b/models/repo/collaboration.go
@@ -40,7 +40,7 @@ type Collaborator struct {
 func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
 	collaborations, err := getCollaborations(ctx, repoID, listOptions)
 	if err != nil {
-		return nil, fmt.Errorf("getCollaborations: %v", err)
+		return nil, fmt.Errorf("getCollaborations: %w", err)
 	}
 
 	collaborators := make([]*Collaborator, 0, len(collaborations))
@@ -114,7 +114,7 @@ func ChangeCollaborationAccessModeCtx(ctx context.Context, repo *Repository, uid
 	}
 	has, err := e.Get(collaboration)
 	if err != nil {
-		return fmt.Errorf("get collaboration: %v", err)
+		return fmt.Errorf("get collaboration: %w", err)
 	} else if !has {
 		return nil
 	}
@@ -128,9 +128,9 @@ func ChangeCollaborationAccessModeCtx(ctx context.Context, repo *Repository, uid
 		ID(collaboration.ID).
 		Cols("mode").
 		Update(collaboration); err != nil {
-		return fmt.Errorf("update collaboration: %v", err)
+		return fmt.Errorf("update collaboration: %w", err)
 	} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
-		return fmt.Errorf("update access table: %v", err)
+		return fmt.Errorf("update access table: %w", err)
 	}
 
 	return nil
diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go
index 2e6253d5610be..cbf46dd286d0a 100644
--- a/models/repo/collaboration_test.go
+++ b/models/repo/collaboration_test.go
@@ -19,7 +19,7 @@ import (
 func TestRepository_GetCollaborators(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	test := func(repoID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		collaborators, err := repo_model.GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{})
 		assert.NoError(t, err)
 		expectedLen, err := db.GetEngine(db.DefaultContext).Count(&repo_model.Collaboration{RepoID: repoID})
@@ -40,7 +40,7 @@ func TestRepository_IsCollaborator(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	test := func(repoID, userID int64, expected bool) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
+		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
 		actual, err := repo_model.IsCollaborator(db.DefaultContext, repo.ID, userID)
 		assert.NoError(t, err)
 		assert.Equal(t, expected, actual)
@@ -54,13 +54,13 @@ func TestRepository_IsCollaborator(t *testing.T) {
 func TestRepository_ChangeCollaborationAccessMode(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
 	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
 
-	collaboration := unittest.AssertExistsAndLoadBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4}).(*repo_model.Collaboration)
+	collaboration := unittest.AssertExistsAndLoadBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4})
 	assert.EqualValues(t, perm.AccessModeAdmin, collaboration.Mode)
 
-	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: repo.ID}).(*access_model.Access)
+	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: repo.ID})
 	assert.EqualValues(t, perm.AccessModeAdmin, access.Mode)
 
 	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
diff --git a/models/repo/mirror.go b/models/repo/mirror.go
index 8f96e8cee1851..297ffd594a720 100644
--- a/models/repo/mirror.go
+++ b/models/repo/mirror.go
@@ -8,7 +8,6 @@ package repo
 import (
 	"context"
 	"errors"
-	"fmt"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
@@ -108,12 +107,14 @@ func DeleteMirrorByRepoID(repoID int64) error {
 
 // MirrorsIterate iterates all mirror repositories.
 func MirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
-	return db.GetEngine(db.DefaultContext).
+	sess := db.GetEngine(db.DefaultContext).
 		Where("next_update_unix<=?", time.Now().Unix()).
 		And("next_update_unix!=0").
-		OrderBy("updated_unix ASC").
-		Limit(limit).
-		Iterate(new(Mirror), f)
+		OrderBy("updated_unix ASC")
+	if limit > 0 {
+		sess = sess.Limit(limit)
+	}
+	return sess.Iterate(new(Mirror), f)
 }
 
 // InsertMirror inserts a mirror to database
@@ -121,53 +122,3 @@ func InsertMirror(ctx context.Context, mirror *Mirror) error {
 	_, err := db.GetEngine(ctx).Insert(mirror)
 	return err
 }
-
-// MirrorRepositoryList contains the mirror repositories
-type MirrorRepositoryList []*Repository
-
-func (repos MirrorRepositoryList) loadAttributes(ctx context.Context) error {
-	if len(repos) == 0 {
-		return nil
-	}
-
-	// Load mirrors.
-	repoIDs := make([]int64, 0, len(repos))
-	for i := range repos {
-		if !repos[i].IsMirror {
-			continue
-		}
-
-		repoIDs = append(repoIDs, repos[i].ID)
-	}
-	mirrors := make([]*Mirror, 0, len(repoIDs))
-	if err := db.GetEngine(ctx).
-		Where("id > 0").
-		In("repo_id", repoIDs).
-		Find(&mirrors); err != nil {
-		return fmt.Errorf("find mirrors: %v", err)
-	}
-
-	set := make(map[int64]*Mirror)
-	for i := range mirrors {
-		set[mirrors[i].RepoID] = mirrors[i]
-	}
-	for i := range repos {
-		repos[i].Mirror = set[repos[i].ID]
-		repos[i].Mirror.Repo = repos[i]
-	}
-	return nil
-}
-
-// LoadAttributes loads the attributes for the given MirrorRepositoryList
-func (repos MirrorRepositoryList) LoadAttributes() error {
-	return repos.loadAttributes(db.DefaultContext)
-}
-
-// GetUserMirrorRepositories returns a list of mirror repositories of given user.
-func GetUserMirrorRepositories(userID int64) ([]*Repository, error) {
-	repos := make([]*Repository, 0, 10)
-	return repos, db.GetEngine(db.DefaultContext).
-		Where("owner_id = ?", userID).
-		And("is_mirror = ?", true).
-		Find(&repos)
-}
diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go
index 048c0c3487b75..38d6e72019700 100644
--- a/models/repo/pushmirror.go
+++ b/models/repo/pushmirror.go
@@ -5,12 +5,15 @@
 package repo
 
 import (
+	"context"
 	"errors"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/builder"
 )
 
 // ErrPushMirrorNotExist mirror does not exist error
@@ -23,11 +26,31 @@ type PushMirror struct {
 	Repo       *Repository `xorm:"-"`
 	RemoteName string
 
+	SyncOnCommit   bool `xorm:"NOT NULL DEFAULT true"`
 	Interval       time.Duration
 	CreatedUnix    timeutil.TimeStamp `xorm:"created"`
 	LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
 	LastError      string             `xorm:"text"`
 }
+type PushMirrorOptions struct {
+	ID         int64
+	RepoID     int64
+	RemoteName string
+}
+
+func (opts *PushMirrorOptions) toConds() builder.Cond {
+	cond := builder.NewCond()
+	if opts.RepoID > 0 {
+		cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
+	}
+	if opts.RemoteName != "" {
+		cond = cond.And(builder.Eq{"remote_name": opts.RemoteName})
+	}
+	if opts.ID > 0 {
+		cond = cond.And(builder.Eq{"id": opts.ID})
+	}
+	return cond
+}
 
 func init() {
 	db.RegisterModel(new(PushMirror))
@@ -52,53 +75,66 @@ func (m *PushMirror) GetRemoteName() string {
 }
 
 // InsertPushMirror inserts a push-mirror to database
-func InsertPushMirror(m *PushMirror) error {
-	_, err := db.GetEngine(db.DefaultContext).Insert(m)
+func InsertPushMirror(ctx context.Context, m *PushMirror) error {
+	_, err := db.GetEngine(ctx).Insert(m)
 	return err
 }
 
 // UpdatePushMirror updates the push-mirror
-func UpdatePushMirror(m *PushMirror) error {
-	_, err := db.GetEngine(db.DefaultContext).ID(m.ID).AllCols().Update(m)
-	return err
-}
-
-// DeletePushMirrorByID deletes a push-mirrors by ID
-func DeletePushMirrorByID(ID int64) error {
-	_, err := db.GetEngine(db.DefaultContext).ID(ID).Delete(&PushMirror{})
+func UpdatePushMirror(ctx context.Context, m *PushMirror) error {
+	_, err := db.GetEngine(ctx).ID(m.ID).AllCols().Update(m)
 	return err
 }
 
-// DeletePushMirrorsByRepoID deletes all push-mirrors by repoID
-func DeletePushMirrorsByRepoID(repoID int64) error {
-	_, err := db.GetEngine(db.DefaultContext).Delete(&PushMirror{RepoID: repoID})
-	return err
+func DeletePushMirrors(ctx context.Context, opts PushMirrorOptions) error {
+	if opts.RepoID > 0 {
+		_, err := db.GetEngine(ctx).Where(opts.toConds()).Delete(&PushMirror{})
+		return err
+	}
+	return errors.New("repoID required and must be set")
 }
 
-// GetPushMirrorByID returns push-mirror information.
-func GetPushMirrorByID(ID int64) (*PushMirror, error) {
-	m := &PushMirror{}
-	has, err := db.GetEngine(db.DefaultContext).ID(ID).Get(m)
+func GetPushMirror(ctx context.Context, opts PushMirrorOptions) (*PushMirror, error) {
+	mirror := &PushMirror{}
+	exist, err := db.GetEngine(ctx).Where(opts.toConds()).Get(mirror)
 	if err != nil {
 		return nil, err
-	} else if !has {
+	} else if !exist {
 		return nil, ErrPushMirrorNotExist
 	}
-	return m, nil
+	return mirror, nil
 }
 
 // GetPushMirrorsByRepoID returns push-mirror information of a repository.
-func GetPushMirrorsByRepoID(repoID int64) ([]*PushMirror, error) {
+func GetPushMirrorsByRepoID(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*PushMirror, int64, error) {
+	sess := db.GetEngine(ctx).Where("repo_id = ?", repoID)
+	if listOptions.Page != 0 {
+		sess = db.SetSessionPagination(sess, &listOptions)
+		mirrors := make([]*PushMirror, 0, listOptions.PageSize)
+		count, err := sess.FindAndCount(&mirrors)
+		return mirrors, count, err
+	}
 	mirrors := make([]*PushMirror, 0, 10)
-	return mirrors, db.GetEngine(db.DefaultContext).Where("repo_id=?", repoID).Find(&mirrors)
+	count, err := sess.FindAndCount(&mirrors)
+	return mirrors, count, err
+}
+
+// GetPushMirrorsSyncedOnCommit returns push-mirrors for this repo that should be updated by new commits
+func GetPushMirrorsSyncedOnCommit(repoID int64) ([]*PushMirror, error) {
+	mirrors := make([]*PushMirror, 0, 10)
+	return mirrors, db.GetEngine(db.DefaultContext).
+		Where("repo_id=? AND sync_on_commit=?", repoID, true).
+		Find(&mirrors)
 }
 
 // PushMirrorsIterate iterates all push-mirror repositories.
-func PushMirrorsIterate(limit int, f func(idx int, bean interface{}) error) error {
-	return db.GetEngine(db.DefaultContext).
+func PushMirrorsIterate(ctx context.Context, limit int, f func(idx int, bean interface{}) error) error {
+	sess := db.GetEngine(ctx).
 		Where("last_update + (`interval` / ?) <= ?", time.Second, time.Now().Unix()).
 		And("`interval` != 0").
-		OrderBy("last_update ASC").
-		Limit(limit).
-		Iterate(new(PushMirror), f)
+		OrderBy("last_update ASC")
+	if limit > 0 {
+		sess = sess.Limit(limit)
+	}
+	return sess.Iterate(new(PushMirror), f)
 }
diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go
index d36a48547e1c6..5087e30095773 100644
--- a/models/repo/pushmirror_test.go
+++ b/models/repo/pushmirror_test.go
@@ -8,6 +8,7 @@ import (
 	"testing"
 	"time"
 
+	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/timeutil"
@@ -20,20 +21,20 @@ func TestPushMirrorsIterate(t *testing.T) {
 
 	now := timeutil.TimeStampNow()
 
-	repo_model.InsertPushMirror(&repo_model.PushMirror{
+	repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
 		RemoteName:     "test-1",
 		LastUpdateUnix: now,
 		Interval:       1,
 	})
 
 	long, _ := time.ParseDuration("24h")
-	repo_model.InsertPushMirror(&repo_model.PushMirror{
+	repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
 		RemoteName:     "test-2",
 		LastUpdateUnix: now,
 		Interval:       long,
 	})
 
-	repo_model.InsertPushMirror(&repo_model.PushMirror{
+	repo_model.InsertPushMirror(db.DefaultContext, &repo_model.PushMirror{
 		RemoteName:     "test-3",
 		LastUpdateUnix: now,
 		Interval:       0,
@@ -41,7 +42,7 @@ func TestPushMirrorsIterate(t *testing.T) {
 
 	time.Sleep(1 * time.Millisecond)
 
-	repo_model.PushMirrorsIterate(1, func(idx int, bean interface{}) error {
+	repo_model.PushMirrorsIterate(db.DefaultContext, 1, func(idx int, bean interface{}) error {
 		m, ok := bean.(*repo_model.PushMirror)
 		assert.True(t, ok)
 		assert.Equal(t, "test-1", m.RemoteName)
diff --git a/models/repo/redirect.go b/models/repo/redirect.go
index 88fad6f3e34ed..f28220c2afea2 100644
--- a/models/repo/redirect.go
+++ b/models/repo/redirect.go
@@ -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"`
diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go
index 05b105cf63ff2..90114667e5991 100644
--- a/models/repo/redirect_test.go
+++ b/models/repo/redirect_test.go
@@ -29,7 +29,7 @@ func TestNewRedirect(t *testing.T) {
 	// redirect to a completely new name
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame"))
 
 	unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
@@ -48,7 +48,7 @@ func TestNewRedirect2(t *testing.T) {
 	// redirect to previously used name
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "oldrepo1"))
 
 	unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
@@ -67,7 +67,7 @@ func TestNewRedirect3(t *testing.T) {
 	// redirect for a previously-unredirected repo
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 	assert.NoError(t, repo_model.NewRedirect(db.DefaultContext, repo.OwnerID, repo.ID, repo.Name, "newreponame"))
 
 	unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{
diff --git a/models/release.go b/models/repo/release.go
similarity index 79%
rename from models/release.go
rename to models/repo/release.go
index 94e803c716193..14428f15f73f6 100644
--- a/models/release.go
+++ b/models/repo/release.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package repo
 
 import (
 	"context"
@@ -14,7 +14,6 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
-	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
@@ -23,14 +22,53 @@ import (
 	"xorm.io/builder"
 )
 
+// ErrReleaseAlreadyExist represents a "ReleaseAlreadyExist" kind of error.
+type ErrReleaseAlreadyExist struct {
+	TagName string
+}
+
+// IsErrReleaseAlreadyExist checks if an error is a ErrReleaseAlreadyExist.
+func IsErrReleaseAlreadyExist(err error) bool {
+	_, ok := err.(ErrReleaseAlreadyExist)
+	return ok
+}
+
+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
+	TagName string
+}
+
+// IsErrReleaseNotExist checks if an error is a ErrReleaseNotExist.
+func IsErrReleaseNotExist(err error) bool {
+	_, ok := err.(ErrReleaseNotExist)
+	return ok
+}
+
+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"`
-	RepoID           int64                  `xorm:"INDEX UNIQUE(n)"`
-	Repo             *repo_model.Repository `xorm:"-"`
-	PublisherID      int64                  `xorm:"INDEX"`
-	Publisher        *user_model.User       `xorm:"-"`
-	TagName          string                 `xorm:"INDEX UNIQUE(n)"`
+	ID               int64            `xorm:"pk autoincr"`
+	RepoID           int64            `xorm:"INDEX UNIQUE(n)"`
+	Repo             *Repository      `xorm:"-"`
+	PublisherID      int64            `xorm:"INDEX"`
+	Publisher        *user_model.User `xorm:"-"`
+	TagName          string           `xorm:"INDEX UNIQUE(n)"`
 	OriginalAuthor   string
 	OriginalAuthorID int64 `xorm:"index"`
 	LowerTagName     string
@@ -38,14 +76,14 @@ type Release struct {
 	Title            string
 	Sha1             string `xorm:"VARCHAR(40)"`
 	NumCommits       int64
-	NumCommitsBehind int64                    `xorm:"-"`
-	Note             string                   `xorm:"TEXT"`
-	RenderedNote     string                   `xorm:"-"`
-	IsDraft          bool                     `xorm:"NOT NULL DEFAULT false"`
-	IsPrerelease     bool                     `xorm:"NOT NULL DEFAULT false"`
-	IsTag            bool                     `xorm:"NOT NULL DEFAULT false"`
-	Attachments      []*repo_model.Attachment `xorm:"-"`
-	CreatedUnix      timeutil.TimeStamp       `xorm:"INDEX"`
+	NumCommitsBehind int64              `xorm:"-"`
+	Note             string             `xorm:"TEXT"`
+	RenderedNote     string             `xorm:"-"`
+	IsDraft          bool               `xorm:"NOT NULL DEFAULT false"`
+	IsPrerelease     bool               `xorm:"NOT NULL DEFAULT false"`
+	IsTag            bool               `xorm:"NOT NULL DEFAULT false"`
+	Attachments      []*Attachment      `xorm:"-"`
+	CreatedUnix      timeutil.TimeStamp `xorm:"INDEX"`
 }
 
 func init() {
@@ -55,7 +93,7 @@ func init() {
 func (r *Release) loadAttributes(ctx context.Context) error {
 	var err error
 	if r.Repo == nil {
-		r.Repo, err = repo_model.GetRepositoryByIDCtx(ctx, r.RepoID)
+		r.Repo, err = GetRepositoryByIDCtx(ctx, r.RepoID)
 		if err != nil {
 			return err
 		}
@@ -116,9 +154,9 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
 // AddReleaseAttachments adds a release attachments
 func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
 	// Check attachments
-	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
+	attachments, err := GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
 	if err != nil {
-		return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
+		return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %w", attachmentUUIDs, err)
 	}
 
 	for i := range attachments {
@@ -128,11 +166,11 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs
 		attachments[i].ReleaseID = releaseID
 		// No assign value could be 0, so ignore AllCols().
 		if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
-			return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
+			return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
 		}
 	}
 
-	return
+	return err
 }
 
 // GetRelease returns release by given ID.
@@ -170,6 +208,7 @@ type FindReleasesOptions struct {
 	IsPreRelease  util.OptionalBool
 	IsDraft       util.OptionalBool
 	TagNames      []string
+	HasSha1       util.OptionalBool // useful to find draft releases which are created with existing tags
 }
 
 func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
@@ -191,6 +230,13 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
 	if !opts.IsDraft.IsNone() {
 		cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
 	}
+	if !opts.HasSha1.IsNone() {
+		if opts.HasSha1.IsTrue() {
+			cond = cond.And(builder.Neq{"sha1": ""})
+		} else {
+			cond = cond.And(builder.Eq{"sha1": ""})
+		}
+	}
 	return cond
 }
 
@@ -279,9 +325,9 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) {
 
 	// Sort
 	sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
-	var attachments []*repo_model.Attachment
+	var attachments []*Attachment
 	for index, element := range rels {
-		element.Attachments = []*repo_model.Attachment{}
+		element.Attachments = []*Attachment{}
 		sortedRels.ID[index] = element.ID
 		sortedRels.Rel[index] = element
 	}
@@ -291,7 +337,7 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) {
 	err = db.GetEngine(ctx).
 		Asc("release_id", "name").
 		In("release_id", sortedRels.ID).
-		Find(&attachments, repo_model.Attachment{})
+		Find(&attachments, Attachment{})
 	if err != nil {
 		return err
 	}
@@ -305,7 +351,7 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) {
 		sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
 	}
 
-	return
+	return err
 }
 
 type releaseSorter struct {
@@ -354,7 +400,7 @@ func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, origi
 }
 
 // PushUpdateDeleteTagsContext updates a number of delete tags with context
-func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error {
+func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []string) error {
 	if len(tags) == 0 {
 		return nil
 	}
@@ -367,7 +413,7 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repositor
 		Where("repo_id = ? AND is_tag = ?", repo.ID, true).
 		In("lower_tag_name", lowerTags).
 		Delete(new(Release)); err != nil {
-		return fmt.Errorf("Delete: %v", err)
+		return fmt.Errorf("Delete: %w", err)
 	}
 
 	if _, err := db.GetEngine(ctx).
@@ -377,31 +423,31 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repositor
 		Update(&Release{
 			IsDraft: true,
 		}); err != nil {
-		return fmt.Errorf("Update: %v", err)
+		return fmt.Errorf("Update: %w", err)
 	}
 
 	return nil
 }
 
 // PushUpdateDeleteTag must be called for any push actions to delete tag
-func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error {
+func PushUpdateDeleteTag(repo *Repository, tagName string) error {
 	rel, err := GetRelease(repo.ID, tagName)
 	if err != nil {
 		if IsErrReleaseNotExist(err) {
 			return nil
 		}
-		return fmt.Errorf("GetRelease: %v", err)
+		return fmt.Errorf("GetRelease: %w", err)
 	}
 	if rel.IsTag {
 		if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil {
-			return fmt.Errorf("Delete: %v", err)
+			return fmt.Errorf("Delete: %w", err)
 		}
 	} else {
 		rel.IsDraft = true
 		rel.NumCommits = 0
 		rel.Sha1 = ""
 		if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
-			return fmt.Errorf("Update: %v", err)
+			return fmt.Errorf("Update: %w", err)
 		}
 	}
 
@@ -409,16 +455,16 @@ func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error {
 }
 
 // SaveOrUpdateTag must be called for any push actions to add tag
-func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error {
+func SaveOrUpdateTag(repo *Repository, newRel *Release) error {
 	rel, err := GetRelease(repo.ID, newRel.TagName)
 	if err != nil && !IsErrReleaseNotExist(err) {
-		return fmt.Errorf("GetRelease: %v", err)
+		return fmt.Errorf("GetRelease: %w", err)
 	}
 
 	if rel == nil {
 		rel = newRel
 		if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil {
-			return fmt.Errorf("InsertOne: %v", err)
+			return fmt.Errorf("InsertOne: %w", err)
 		}
 	} else {
 		rel.Sha1 = newRel.Sha1
@@ -429,7 +475,7 @@ func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error {
 			rel.PublisherID = newRel.PublisherID
 		}
 		if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
-			return fmt.Errorf("Update: %v", err)
+			return fmt.Errorf("Update: %w", err)
 		}
 	}
 	return nil
diff --git a/models/repo/repo.go b/models/repo/repo.go
index f6097d2d6a428..77e0367a5a5c7 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -23,9 +23,11 @@ import (
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
+
+	"xorm.io/builder"
 )
 
-// ErrUserDoesNotHaveAccessToRepo represets an error where the user doesn't has access to a given repo.
+// ErrUserDoesNotHaveAccessToRepo represents an error where the user doesn't has access to a given repo.
 type ErrUserDoesNotHaveAccessToRepo struct {
 	UserID   int64
 	RepoName string
@@ -41,6 +43,10 @@ func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
 	return fmt.Sprintf("user doesn't have access to repo [user_id: %d, repo_name: %s]", err.UserID, err.RepoName)
 }
 
+func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 var (
 	reservedRepoNames    = []string{".", "..", "-"}
 	reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
@@ -280,7 +286,7 @@ func (repo *Repository) CommitLink(commitID string) (result string) {
 	} else {
 		result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID)
 	}
-	return
+	return result
 }
 
 // APIURL returns the repository API URL
@@ -319,13 +325,7 @@ func (repo *Repository) LoadUnits(ctx context.Context) (err error) {
 
 // UnitEnabled if this repository has the given unit enabled
 func (repo *Repository) UnitEnabled(tp unit.Type) (result bool) {
-	if err := db.WithContext(func(ctx *db.Context) error {
-		result = repo.UnitEnabledCtx(ctx, tp)
-		return nil
-	}); err != nil {
-		log.Error("repo.UnitEnabled: %v", err)
-	}
-	return
+	return repo.UnitEnabledCtx(db.DefaultContext, tp)
 }
 
 // UnitEnabled if this repository has the given unit enabled
@@ -546,7 +546,7 @@ func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
 		log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
 		return template.HTML(markup.Sanitize(repo.Description))
 	}
-	return template.HTML(markup.Sanitize(string(desc)))
+	return template.HTML(markup.Sanitize(desc))
 }
 
 // CloneLink represents different types of clone URLs of repository.
@@ -647,6 +647,11 @@ func (err ErrRepoNotExist) Error() string {
 		err.ID, err.UID, err.OwnerName, err.Name)
 }
 
+// Unwrap unwraps this error as a ErrNotExist error
+func (err ErrRepoNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetRepositoryByOwnerAndNameCtx returns the repository by given owner name and repo name
 func GetRepositoryByOwnerAndNameCtx(ctx context.Context, ownerName, repoName string) (*Repository, error) {
 	var repo Repository
@@ -755,38 +760,45 @@ func CountRepositories(ctx context.Context, opts CountRepositoryOptions) (int64,
 
 	count, err := sess.Count(new(Repository))
 	if err != nil {
-		return 0, fmt.Errorf("countRepositories: %v", err)
+		return 0, fmt.Errorf("countRepositories: %w", err)
 	}
 	return count, nil
 }
 
-// StatsCorrectNumClosed update repository's issue related numbers
-func StatsCorrectNumClosed(ctx context.Context, id int64, isPull bool, field string) error {
-	_, err := db.Exec(ctx, "UPDATE `repository` SET "+field+"=(SELECT COUNT(*) FROM `issue` WHERE repo_id=? AND is_closed=? AND is_pull=?) WHERE id=?", id, true, isPull, id)
-	return err
-}
-
-// UpdateRepoIssueNumbers update repository issue numbers
+// UpdateRepoIssueNumbers updates one of a repositories amount of (open|closed) (issues|PRs) with the current count
 func UpdateRepoIssueNumbers(ctx context.Context, repoID int64, isPull, isClosed bool) error {
-	e := db.GetEngine(ctx)
+	field := "num_"
+	if isClosed {
+		field += "closed_"
+	}
 	if isPull {
-		if _, err := e.ID(repoID).Decr("num_pulls").Update(new(Repository)); err != nil {
-			return err
-		}
-		if isClosed {
-			if _, err := e.ID(repoID).Decr("num_closed_pulls").Update(new(Repository)); err != nil {
-				return err
-			}
-		}
+		field += "pulls"
 	} else {
-		if _, err := e.ID(repoID).Decr("num_issues").Update(new(Repository)); err != nil {
-			return err
-		}
-		if isClosed {
-			if _, err := e.ID(repoID).Decr("num_closed_issues").Update(new(Repository)); err != nil {
-				return err
-			}
-		}
+		field += "issues"
 	}
-	return nil
+
+	subQuery := builder.Select("count(*)").
+		From("issue").Where(builder.Eq{
+		"repo_id": repoID,
+		"is_pull": isPull,
+	}.And(builder.If(isClosed, builder.Eq{"is_closed": isClosed})))
+
+	// builder.Update(cond) will generate SQL like UPDATE ... SET cond
+	query := builder.Update(builder.Eq{field: subQuery}).
+		From("repository").
+		Where(builder.Eq{"id": repoID})
+	_, err := db.Exec(ctx, query)
+	return err
+}
+
+// CountNullArchivedRepository counts the number of repositories with is_archived is null
+func CountNullArchivedRepository() (int64, error) {
+	return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Count(new(Repository))
+}
+
+// FixNullArchivedRepository sets is_archived to false where it is null
+func FixNullArchivedRepository() (int64, error) {
+	return db.GetEngine(db.DefaultContext).Where(builder.IsNull{"is_archived"}).Cols("is_archived").NoAutoTime().Update(&Repository{
+		IsArchived: false,
+	})
 }
diff --git a/models/repo/repo_indexer.go b/models/repo/repo_indexer.go
index cba70a14e55f2..67ba3382dc6e3 100644
--- a/models/repo/repo_indexer.go
+++ b/models/repo/repo_indexer.go
@@ -95,13 +95,13 @@ func GetIndexerStatus(ctx context.Context, repo *Repository, indexerType RepoInd
 func UpdateIndexerStatus(ctx context.Context, repo *Repository, indexerType RepoIndexerType, sha string) error {
 	status, err := GetIndexerStatus(ctx, repo, indexerType)
 	if err != nil {
-		return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s Error: %v", repo.FullName(), err)
+		return fmt.Errorf("UpdateIndexerStatus: Unable to getIndexerStatus for repo: %s Error: %w", repo.FullName(), err)
 	}
 
 	if len(status.CommitSha) == 0 {
 		status.CommitSha = sha
 		if err := db.Insert(ctx, status); err != nil {
-			return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err)
+			return fmt.Errorf("UpdateIndexerStatus: Unable to insert repoIndexerStatus for repo: %s Sha: %s Error: %w", repo.FullName(), sha, err)
 		}
 		return nil
 	}
@@ -109,7 +109,7 @@ func UpdateIndexerStatus(ctx context.Context, repo *Repository, indexerType Repo
 	_, err = db.GetEngine(ctx).ID(status.ID).Cols("commit_sha").
 		Update(status)
 	if err != nil {
-		return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s Sha: %s Error: %v", repo.FullName(), sha, err)
+		return fmt.Errorf("UpdateIndexerStatus: Unable to update repoIndexerStatus for repo: %s Sha: %s Error: %w", repo.FullName(), sha, err)
 	}
 	return nil
 }
diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go
index a70fc8efd409a..191970d275681 100644
--- a/models/repo/repo_list.go
+++ b/models/repo/repo_list.go
@@ -6,6 +6,7 @@ package repo
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 
@@ -14,36 +15,12 @@ import (
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/container"
-	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
 
-// IterateRepository iterate repositories
-func IterateRepository(f func(repo *Repository) error) error {
-	var start int
-	batchSize := setting.Database.IterateBufferSize
-	sess := db.GetEngine(db.DefaultContext)
-	for {
-		repos := make([]*Repository, 0, batchSize)
-		if err := sess.Limit(batchSize, start).Find(&repos); err != nil {
-			return err
-		}
-		if len(repos) == 0 {
-			return nil
-		}
-		start += len(repos)
-
-		for _, repo := range repos {
-			if err := f(repo); err != nil {
-				return err
-			}
-		}
-	}
-}
-
 // FindReposMapByIDs find repos as map
 func FindReposMapByIDs(repoIDs []int64, res map[int64]*Repository) error {
 	return db.GetEngine(db.DefaultContext).In("id", repoIDs).Find(&res)
@@ -91,10 +68,10 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
 		return nil
 	}
 
-	set := make(map[int64]struct{})
+	set := make(container.Set[int64])
 	repoIDs := make([]int64, len(repos))
 	for i := range repos {
-		set[repos[i].OwnerID] = struct{}{}
+		set.Add(repos[i].OwnerID)
 		repoIDs[i] = repos[i].ID
 	}
 
@@ -102,9 +79,9 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
 	users := make(map[int64]*user_model.User, len(set))
 	if err := db.GetEngine(ctx).
 		Where("id > 0").
-		In("id", container.KeysInt64(set)).
+		In("id", set.Values()).
 		Find(&users); err != nil {
-		return fmt.Errorf("find users: %v", err)
+		return fmt.Errorf("find users: %w", err)
 	}
 	for i := range repos {
 		repos[i].Owner = users[repos[i].OwnerID]
@@ -116,7 +93,7 @@ func (repos RepositoryList) loadAttributes(ctx context.Context) error {
 		Where("`is_primary` = ? AND `language` != ?", true, "other").
 		In("`repo_id`", repoIDs).
 		Find(&stats); err != nil {
-		return fmt.Errorf("find primary languages: %v", err)
+		return fmt.Errorf("find primary languages: %w", err)
 	}
 	stats.LoadAttributes()
 	for i := range repos {
@@ -186,6 +163,10 @@ type SearchRepoOptions struct {
 	HasMilestones util.OptionalBool
 	// LowerNames represents valid lower names to restrict to
 	LowerNames []string
+	// When specified true, apply some filters over the conditions:
+	// - Don't show forks, when opts.Fork is OptionalBoolNone.
+	// - Do not display repositories that don't have a description, an icon and topics.
+	OnlyShowRelevant bool
 }
 
 // SearchOrderBy is used to sort the result
@@ -486,8 +467,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 			Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
 	}
 
-	if opts.Fork != util.OptionalBoolNone {
-		cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue})
+	if opts.Fork != util.OptionalBoolNone || opts.OnlyShowRelevant {
+		if opts.OnlyShowRelevant && opts.Fork == util.OptionalBoolNone {
+			cond = cond.And(builder.Eq{"is_fork": false})
+		} else {
+			cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue})
+		}
 	}
 
 	if opts.Mirror != util.OptionalBoolNone {
@@ -509,6 +494,25 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 		cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
 	}
 
+	if opts.OnlyShowRelevant {
+		// Only show a repo that either has a topic or description.
+		subQueryCond := builder.NewCond()
+
+		// Topic checking. Topics is non-null.
+		subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
+
+		// Description checking. Description not empty.
+		subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
+
+		// Repo has a avatar.
+		subQueryCond = subQueryCond.Or(builder.Neq{"avatar": ""})
+
+		// Always hide repo's that are empty.
+		subQueryCond = subQueryCond.And(builder.Eq{"is_empty": false})
+
+		cond = cond.And(subQueryCond)
+	}
+
 	return cond
 }
 
@@ -533,7 +537,7 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loa
 	}
 	repos := make(RepositoryList, 0, defaultSize)
 	if err := sess.Find(&repos); err != nil {
-		return nil, 0, fmt.Errorf("Repo: %v", err)
+		return nil, 0, fmt.Errorf("Repo: %w", err)
 	}
 
 	if opts.PageSize <= 0 {
@@ -542,7 +546,7 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loa
 
 	if loadAttributes {
 		if err := repos.loadAttributes(ctx); err != nil {
-			return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
+			return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
 		}
 	}
 
@@ -578,7 +582,7 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
 			Where(cond).
 			Count(new(Repository))
 		if err != nil {
-			return nil, 0, fmt.Errorf("Count: %v", err)
+			return nil, 0, fmt.Errorf("Count: %w", err)
 		}
 	}
 
@@ -589,6 +593,16 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
 	return sess, count, nil
 }
 
+// SearchRepositoryIDsByCondition search repository IDs by given condition.
+func SearchRepositoryIDsByCondition(ctx context.Context, cond builder.Cond) ([]int64, error) {
+	repoIDs := make([]int64, 0, 10)
+	return repoIDs, db.GetEngine(ctx).
+		Table("repository").
+		Cols("id").
+		Where(cond).
+		Find(&repoIDs)
+}
+
 // AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
 func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond {
 	cond := builder.NewCond()
@@ -676,16 +690,16 @@ func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder {
 }
 
 // FindUserCodeAccessibleRepoIDs finds all at Code level accessible repositories' ID by the user's id
-func FindUserCodeAccessibleRepoIDs(user *user_model.User) ([]int64, error) {
-	repoIDs := make([]int64, 0, 10)
-	if err := db.GetEngine(db.DefaultContext).
-		Table("repository").
-		Cols("id").
-		Where(AccessibleRepositoryCondition(user, unit.TypeCode)).
-		Find(&repoIDs); err != nil {
-		return nil, fmt.Errorf("FindUserCodeAccesibleRepoIDs: %v", err)
-	}
-	return repoIDs, nil
+func FindUserCodeAccessibleRepoIDs(ctx context.Context, user *user_model.User) ([]int64, error) {
+	return SearchRepositoryIDsByCondition(ctx, AccessibleRepositoryCondition(user, unit.TypeCode))
+}
+
+// FindUserCodeAccessibleOwnerRepoIDs finds all repository IDs for the given owner whose code the user can see.
+func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user *user_model.User) ([]int64, error) {
+	return SearchRepositoryIDsByCondition(ctx, builder.NewCond().And(
+		builder.Eq{"owner_id": ownerID},
+		AccessibleRepositoryCondition(user, unit.TypeCode),
+	))
 }
 
 // GetUserRepositories returns a list of repositories of given user.
@@ -695,6 +709,9 @@ func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error)
 	}
 
 	cond := builder.NewCond()
+	if opts.Actor == nil {
+		return nil, 0, errors.New("GetUserRepositories: Actor is needed but not given")
+	}
 	cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID})
 	if !opts.Private {
 		cond = cond.And(builder.Eq{"is_private": false})
@@ -708,7 +725,7 @@ func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error)
 
 	count, err := sess.Where(cond).Count(new(Repository))
 	if err != nil {
-		return nil, 0, fmt.Errorf("Count: %v", err)
+		return nil, 0, fmt.Errorf("Count: %w", err)
 	}
 
 	sess = sess.Where(cond).OrderBy(opts.OrderBy.String())
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index 6f8b282a66aa1..617ec12798d4b 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -56,7 +56,7 @@ func TestGetPrivateRepositoryCount(t *testing.T) {
 
 func TestRepoAPIURL(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
 
 	assert.Equal(t, "https://try.gitea.io/api/v1/repos/user12/repo10", repo.APIURL())
 }
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index da3e19decea82..dd85ca9186fcf 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/xorm"
 	"xorm.io/xorm/convert"
@@ -33,6 +34,10 @@ func (err ErrUnitTypeNotExist) Error() string {
 	return fmt.Sprintf("Unit type does not exist: %s", err.UT.String())
 }
 
+func (err ErrUnitTypeNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // RepoUnit describes all units of a repository
 type RepoUnit struct { //revive:disable-line:exported
 	ID          int64
diff --git a/models/repo/star_test.go b/models/repo/star_test.go
index aa72b1dac8c0d..1b53e17d27325 100644
--- a/models/repo/star_test.go
+++ b/models/repo/star_test.go
@@ -36,7 +36,7 @@ func TestIsStaring(t *testing.T) {
 func TestRepository_GetStargazers(t *testing.T) {
 	// repo with stargazers
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
 	gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0})
 	assert.NoError(t, err)
 	if assert.Len(t, gazers, 1) {
@@ -47,7 +47,7 @@ func TestRepository_GetStargazers(t *testing.T) {
 func TestRepository_GetStargazers2(t *testing.T) {
 	// repo with stargazers
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 	gazers, err := repo_model.GetStargazers(repo, db.ListOptions{Page: 0})
 	assert.NoError(t, err)
 	assert.Len(t, gazers, 0)
diff --git a/models/repo/topic.go b/models/repo/topic.go
index 2a16467215d3a..33bbb05af9316 100644
--- a/models/repo/topic.go
+++ b/models/repo/topic.go
@@ -11,7 +11,9 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -54,6 +56,10 @@ func (err ErrTopicNotExist) Error() string {
 	return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
 }
 
+func (err ErrTopicNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ValidateTopic checks a topic by length and match pattern rules
 func ValidateTopic(topic string) bool {
 	return len(topic) <= 35 && topicPattern.MatchString(topic)
@@ -62,7 +68,7 @@ func ValidateTopic(topic string) bool {
 // SanitizeAndValidateTopics sanitizes and checks an array or topics
 func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []string) {
 	validTopics = make([]string, 0)
-	mValidTopics := make(map[string]struct{})
+	mValidTopics := make(container.Set[string])
 	invalidTopics = make([]string, 0)
 
 	for _, topic := range topics {
@@ -72,12 +78,12 @@ func SanitizeAndValidateTopics(topics []string) (validTopics, invalidTopics []st
 			continue
 		}
 		// ignore same topic twice
-		if _, ok := mValidTopics[topic]; ok {
+		if mValidTopics.Contains(topic) {
 			continue
 		}
 		if ValidateTopic(topic) {
 			validTopics = append(validTopics, topic)
-			mValidTopics[topic] = struct{}{}
+			mValidTopics.Add(topic)
 		} else {
 			invalidTopics = append(invalidTopics, topic)
 		}
diff --git a/models/repo/update.go b/models/repo/update.go
index 07776ebc01941..cc21deb0bc4f1 100644
--- a/models/repo/update.go
+++ b/models/repo/update.go
@@ -63,6 +63,10 @@ func (err ErrReachLimitOfRepo) Error() string {
 	return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit)
 }
 
+func (err ErrReachLimitOfRepo) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrRepoAlreadyExist represents a "RepoAlreadyExist" kind of error.
 type ErrRepoAlreadyExist struct {
 	Uname string
@@ -79,6 +83,10 @@ func (err ErrRepoAlreadyExist) Error() string {
 	return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
+func (err ErrRepoAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error.
 type ErrRepoFilesAlreadyExist struct {
 	Uname string
@@ -95,6 +103,10 @@ func (err ErrRepoFilesAlreadyExist) Error() string {
 	return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
+func (err ErrRepoFilesAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // CheckCreateRepository check if could created a repository
 func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdopt bool) error {
 	if !doer.CanCreateRepo() {
@@ -107,7 +119,7 @@ func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdo
 
 	has, err := IsRepositoryExist(db.DefaultContext, u, name)
 	if err != nil {
-		return fmt.Errorf("IsRepositoryExist: %v", err)
+		return fmt.Errorf("IsRepositoryExist: %w", err)
 	} else if has {
 		return ErrRepoAlreadyExist{u.Name, name}
 	}
@@ -138,14 +150,14 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
 
 	has, err := IsRepositoryExist(db.DefaultContext, repo.Owner, newRepoName)
 	if err != nil {
-		return fmt.Errorf("IsRepositoryExist: %v", err)
+		return fmt.Errorf("IsRepositoryExist: %w", err)
 	} else if has {
 		return ErrRepoAlreadyExist{repo.Owner.Name, newRepoName}
 	}
 
 	newRepoPath := RepoPath(repo.Owner.Name, newRepoName)
 	if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil {
-		return fmt.Errorf("rename repository directory: %v", err)
+		return fmt.Errorf("rename repository directory: %w", err)
 	}
 
 	wikiPath := repo.WikiPath()
@@ -156,7 +168,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
 	}
 	if isExist {
 		if err = util.Rename(wikiPath, WikiPath(repo.Owner.Name, newRepoName)); err != nil {
-			return fmt.Errorf("rename repository wiki: %v", err)
+			return fmt.Errorf("rename repository wiki: %w", err)
 		}
 	}
 
diff --git a/models/upload.go b/models/repo/upload.go
similarity index 79%
rename from models/upload.go
rename to models/repo/upload.go
index 4a64ff34e3f38..e115c8e50eceb 100644
--- a/models/upload.go
+++ b/models/repo/upload.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package repo
 
 import (
 	"fmt"
@@ -20,13 +20,25 @@ import (
 	gouuid "github.com/google/uuid"
 )
 
-//  ____ ___        .__                    .___ ___________.___.__
-// |    |   \______ |  |   _________     __| _/ \_   _____/|   |  |   ____   ______
-// |    |   /\____ \|  |  /  _ \__  \   / __ |   |    __)  |   |  | _/ __ \ /  ___/
-// |    |  / |  |_> >  |_(  <_> ) __ \_/ /_/ |   |     \   |   |  |_\  ___/ \___ \
-// |______/  |   __/|____/\____(____  /\____ |   \___  /   |___|____/\___  >____  >
-//           |__|                   \/      \/       \/                  \/     \/
-//
+// ErrUploadNotExist represents a "UploadNotExist" kind of error.
+type ErrUploadNotExist struct {
+	ID   int64
+	UUID string
+}
+
+// IsErrUploadNotExist checks if an error is a ErrUploadNotExist.
+func IsErrUploadNotExist(err error) bool {
+	_, ok := err.(ErrUploadNotExist)
+	return ok
+}
+
+func (err ErrUploadNotExist) Error() string {
+	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
+}
+
+func (err ErrUploadNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
 
 // Upload represent a uploaded file to a repo to be deleted when moved
 type Upload struct {
@@ -58,19 +70,19 @@ func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err err
 
 	localPath := upload.LocalPath()
 	if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
-		return nil, fmt.Errorf("MkdirAll: %v", err)
+		return nil, fmt.Errorf("MkdirAll: %w", err)
 	}
 
 	fw, err := os.Create(localPath)
 	if err != nil {
-		return nil, fmt.Errorf("Create: %v", err)
+		return nil, fmt.Errorf("Create: %w", err)
 	}
 	defer fw.Close()
 
 	if _, err = fw.Write(buf); err != nil {
-		return nil, fmt.Errorf("Write: %v", err)
+		return nil, fmt.Errorf("Write: %w", err)
 	} else if _, err = io.Copy(fw, file); err != nil {
-		return nil, fmt.Errorf("Copy: %v", err)
+		return nil, fmt.Errorf("Copy: %w", err)
 	}
 
 	if _, err := db.GetEngine(db.DefaultContext).Insert(upload); err != nil {
@@ -122,7 +134,7 @@ func DeleteUploads(uploads ...*Upload) (err error) {
 	if _, err = db.GetEngine(ctx).
 		In("id", ids).
 		Delete(new(Upload)); err != nil {
-		return fmt.Errorf("delete uploads: %v", err)
+		return fmt.Errorf("delete uploads: %w", err)
 	}
 
 	if err = committer.Commit(); err != nil {
@@ -140,7 +152,7 @@ func DeleteUploads(uploads ...*Upload) (err error) {
 		}
 
 		if err := util.Remove(localPath); err != nil {
-			return fmt.Errorf("remove upload: %v", err)
+			return fmt.Errorf("remove upload: %w", err)
 		}
 	}
 
@@ -154,11 +166,11 @@ func DeleteUploadByUUID(uuid string) error {
 		if IsErrUploadNotExist(err) {
 			return nil
 		}
-		return fmt.Errorf("GetUploadByUUID: %v", err)
+		return fmt.Errorf("GetUploadByUUID: %w", err)
 	}
 
 	if err := DeleteUploads(upload); err != nil {
-		return fmt.Errorf("DeleteUpload: %v", err)
+		return fmt.Errorf("DeleteUpload: %w", err)
 	}
 
 	return nil
diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go
index e697505b81e4e..e7125f70f8f94 100644
--- a/models/repo/user_repo.go
+++ b/models/repo/user_repo.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/container"
 	api "code.gitea.io/gitea/modules/structs"
 
 	"xorm.io/builder"
@@ -83,37 +84,19 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
 		return nil, err
 	}
 
-	uidMap := map[int64]bool{}
-	i := 0
-	for _, uid := range userIDs {
-		if uidMap[uid] {
-			continue
-		}
-		uidMap[uid] = true
-		userIDs[i] = uid
-		i++
-	}
-	userIDs = userIDs[:i]
-	userIDs = append(userIDs, additionalUserIDs...)
-
-	for _, uid := range additionalUserIDs {
-		if uidMap[uid] {
-			continue
-		}
-		userIDs[i] = uid
-		i++
-	}
-	userIDs = userIDs[:i]
+	uniqueUserIDs := make(container.Set[int64])
+	uniqueUserIDs.AddMultiple(userIDs...)
+	uniqueUserIDs.AddMultiple(additionalUserIDs...)
 
 	// Leave a seat for owner itself to append later, but if owner is an organization
 	// and just waste 1 unit is cheaper than re-allocate memory once.
-	users := make([]*user_model.User, 0, len(userIDs)+1)
+	users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
 	if len(userIDs) > 0 {
-		if err = e.In("id", userIDs).Find(&users); err != nil {
+		if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
 			return nil, err
 		}
 	}
-	if !repo.Owner.IsOrganization() && !uidMap[repo.OwnerID] {
+	if !repo.Owner.IsOrganization() && !uniqueUserIDs.Contains(repo.OwnerID) {
 		users = append(users, repo.Owner)
 	}
 
@@ -168,5 +151,17 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
 	}
 
 	users := make([]*user_model.User, 0, 8)
-	return users, db.GetEngine(ctx).Where(cond).OrderBy("name").Find(&users)
+	return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
+}
+
+// GetIssuePosters returns all users that have authored an issue/pull request for the given repository
+func GetIssuePosters(ctx context.Context, repo *Repository, isPull bool) ([]*user_model.User, error) {
+	users := make([]*user_model.User, 0, 8)
+	cond := builder.In("`user`.id",
+		builder.Select("poster_id").From("issue").Where(
+			builder.Eq{"repo_id": repo.ID}.
+				And(builder.Eq{"is_pull": isPull}),
+		).GroupBy("poster_id"),
+	)
+	return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
 }
diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go
index d024729b9cde4..6409145920907 100644
--- a/models/repo/user_repo_test.go
+++ b/models/repo/user_repo_test.go
@@ -17,13 +17,13 @@ import (
 func TestRepoAssignees(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 	users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2)
 	assert.NoError(t, err)
 	assert.Len(t, users, 1)
 	assert.Equal(t, users[0].ID, int64(2))
 
-	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}).(*repo_model.Repository)
+	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
 	users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
 	assert.NoError(t, err)
 	assert.Len(t, users, 3)
@@ -36,7 +36,7 @@ func TestRepoGetReviewers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	// test public repo
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 
 	ctx := db.DefaultContext
 	reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
@@ -54,7 +54,7 @@ func TestRepoGetReviewers(t *testing.T) {
 	assert.Len(t, reviewers, 3)
 
 	// test private user repo
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 
 	reviewers, err = repo_model.GetReviewers(ctx, repo2, 2, 4)
 	assert.NoError(t, err)
@@ -62,7 +62,7 @@ func TestRepoGetReviewers(t *testing.T) {
 	assert.EqualValues(t, reviewers[0].ID, 2)
 
 	// test private org repo
-	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 
 	reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 1)
 	assert.NoError(t, err)
diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go
index 3875e63fd873b..18a2d5d5fdf3f 100644
--- a/models/repo/watch_test.go
+++ b/models/repo/watch_test.go
@@ -30,7 +30,7 @@ func TestIsWatching(t *testing.T) {
 func TestGetWatchers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	watches, err := repo_model.GetWatchers(db.DefaultContext, repo.ID)
 	assert.NoError(t, err)
 	// One watchers are inactive, thus minus 1
@@ -47,7 +47,7 @@ func TestGetWatchers(t *testing.T) {
 func TestRepository_GetWatchers(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1})
 	assert.NoError(t, err)
 	assert.Len(t, watchers, repo.NumWatches)
@@ -55,7 +55,7 @@ func TestRepository_GetWatchers(t *testing.T) {
 		unittest.AssertExistsAndLoadBean(t, &repo_model.Watch{UserID: watcher.ID, RepoID: repo.ID})
 	}
 
-	repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}).(*repo_model.Repository)
+	repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9})
 	watchers, err = repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1})
 	assert.NoError(t, err)
 	assert.Len(t, watchers, 0)
@@ -64,7 +64,7 @@ func TestRepository_GetWatchers(t *testing.T) {
 func TestWatchIfAuto(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	watchers, err := repo_model.GetRepoWatchers(repo.ID, db.ListOptions{Page: 1})
 	assert.NoError(t, err)
 	assert.Len(t, watchers, repo.NumWatches)
diff --git a/models/repo/wiki.go b/models/repo/wiki.go
index abf0155cadc2b..c8886eaa3454e 100644
--- a/models/repo/wiki.go
+++ b/models/repo/wiki.go
@@ -6,6 +6,7 @@
 package repo
 
 import (
+	"fmt"
 	"path/filepath"
 	"strings"
 
@@ -14,6 +15,63 @@ import (
 	"code.gitea.io/gitea/modules/util"
 )
 
+// ErrWikiAlreadyExist represents a "WikiAlreadyExist" kind of error.
+type ErrWikiAlreadyExist struct {
+	Title string
+}
+
+// IsErrWikiAlreadyExist checks if an error is an ErrWikiAlreadyExist.
+func IsErrWikiAlreadyExist(err error) bool {
+	_, ok := err.(ErrWikiAlreadyExist)
+	return ok
+}
+
+func (err ErrWikiAlreadyExist) Error() string {
+	return fmt.Sprintf("wiki page already exists [title: %s]", err.Title)
+}
+
+func (err ErrWikiAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
+// ErrWikiReservedName represents a reserved name error.
+type ErrWikiReservedName struct {
+	Title string
+}
+
+// IsErrWikiReservedName checks if an error is an ErrWikiReservedName.
+func IsErrWikiReservedName(err error) bool {
+	_, ok := err.(ErrWikiReservedName)
+	return ok
+}
+
+func (err ErrWikiReservedName) Error() string {
+	return fmt.Sprintf("wiki title is reserved: %s", err.Title)
+}
+
+func (err ErrWikiReservedName) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
+// ErrWikiInvalidFileName represents an invalid wiki file name.
+type ErrWikiInvalidFileName struct {
+	FileName string
+}
+
+// IsErrWikiInvalidFileName checks if an error is an ErrWikiInvalidFileName.
+func IsErrWikiInvalidFileName(err error) bool {
+	_, ok := err.(ErrWikiInvalidFileName)
+	return ok
+}
+
+func (err ErrWikiInvalidFileName) Error() string {
+	return fmt.Sprintf("Invalid wiki filename: %s", err.FileName)
+}
+
+func (err ErrWikiInvalidFileName) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // WikiCloneLink returns clone URLs of repository wiki.
 func (repo *Repository) WikiCloneLink() *CloneLink {
 	return repo.cloneLink(true)
diff --git a/models/repo/wiki_test.go b/models/repo/wiki_test.go
index 339289e05d866..8631736276289 100644
--- a/models/repo/wiki_test.go
+++ b/models/repo/wiki_test.go
@@ -18,7 +18,7 @@ import (
 func TestRepository_WikiCloneLink(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	cloneLink := repo.WikiCloneLink()
 	assert.Equal(t, "ssh://sshuser@try.gitea.io:3000/user2/repo1.wiki.git", cloneLink.SSH)
 	assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
@@ -32,15 +32,15 @@ func TestWikiPath(t *testing.T) {
 
 func TestRepository_WikiPath(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
 	assert.Equal(t, expected, repo.WikiPath())
 }
 
 func TestRepository_HasWiki(t *testing.T) {
 	unittest.PrepareTestEnv(t)
-	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	assert.True(t, repo1.HasWiki())
-	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 	assert.False(t, repo2.HasWiki())
 }
diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go
index c8866421bd0a3..58d6b0f488c43 100644
--- a/models/repo_collaboration.go
+++ b/models/repo_collaboration.go
@@ -11,7 +11,6 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
-	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -19,42 +18,6 @@ import (
 	"xorm.io/builder"
 )
 
-func addCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error {
-	collaboration := &repo_model.Collaboration{
-		RepoID: repo.ID,
-		UserID: u.ID,
-	}
-
-	has, err := db.GetByBean(ctx, collaboration)
-	if err != nil {
-		return err
-	} else if has {
-		return nil
-	}
-	collaboration.Mode = perm.AccessModeWrite
-
-	if err = db.Insert(ctx, collaboration); err != nil {
-		return err
-	}
-
-	return access_model.RecalculateUserAccess(ctx, repo, u.ID)
-}
-
-// AddCollaborator adds new collaboration to a repository with default access mode.
-func AddCollaborator(repo *repo_model.Repository, u *user_model.User) error {
-	ctx, committer, err := db.TxContext()
-	if err != nil {
-		return err
-	}
-	defer committer.Close()
-
-	if err := addCollaborator(ctx, repo, u); err != nil {
-		return err
-	}
-
-	return committer.Commit()
-}
-
 // DeleteCollaboration removes collaboration relation between the user and repository.
 func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
 	collaboration := &repo_model.Collaboration{
@@ -103,7 +66,7 @@ func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Reposito
 	if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
 		In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
 		Delete(&issues_model.IssueAssignees{}); err != nil {
-		return fmt.Errorf("Could not delete assignee[%d] %v", uid, err)
+		return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
 	}
 	return nil
 }
diff --git a/models/repo_collaboration_test.go b/models/repo_collaboration_test.go
index 4cf4d612184c1..77034b65d2296 100644
--- a/models/repo_collaboration_test.go
+++ b/models/repo_collaboration_test.go
@@ -10,30 +10,14 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
-	user_model "code.gitea.io/gitea/models/user"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestRepository_AddCollaborator(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	testSuccess := func(repoID, userID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
-		assert.NoError(t, repo.GetOwner(db.DefaultContext))
-		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}).(*user_model.User)
-		assert.NoError(t, AddCollaborator(repo, user))
-		unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}, &user_model.User{ID: userID})
-	}
-	testSuccess(1, 4)
-	testSuccess(1, 4)
-	testSuccess(3, 4)
-}
-
 func TestRepository_DeleteCollaboration(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
 	assert.NoError(t, repo.GetOwner(db.DefaultContext))
 	assert.NoError(t, DeleteCollaboration(repo, 4))
 	unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4})
diff --git a/models/repo_transfer.go b/models/repo_transfer.go
index 7d07fb252ca20..d6a3985fe5f28 100644
--- a/models/repo_transfer.go
+++ b/models/repo_transfer.go
@@ -179,7 +179,7 @@ func CreatePendingRepositoryTransfer(doer, newOwner *user_model.User, repoID int
 
 	// Check if new owner has repository with same name.
 	if has, err := repo_model.IsRepositoryExist(ctx, newOwner, repo.Name); err != nil {
-		return fmt.Errorf("IsRepositoryExist: %v", err)
+		return fmt.Errorf("IsRepositoryExist: %w", err)
 	} else if has {
 		return repo_model.ErrRepoAlreadyExist{
 			Uname: newOwner.LowerName,
@@ -253,13 +253,13 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 
 	newOwner, err := user_model.GetUserByName(ctx, newOwnerName)
 	if err != nil {
-		return fmt.Errorf("get new owner '%s': %v", newOwnerName, err)
+		return fmt.Errorf("get new owner '%s': %w", newOwnerName, err)
 	}
 	newOwnerName = newOwner.Name // ensure capitalisation matches
 
 	// Check if new owner has repository with same name.
 	if has, err := repo_model.IsRepositoryExist(ctx, newOwner, repo.Name); err != nil {
-		return fmt.Errorf("IsRepositoryExist: %v", err)
+		return fmt.Errorf("IsRepositoryExist: %w", err)
 	} else if has {
 		return repo_model.ErrRepoAlreadyExist{
 			Uname: newOwnerName,
@@ -278,13 +278,13 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 
 	// Update repository.
 	if _, err := sess.ID(repo.ID).Update(repo); err != nil {
-		return fmt.Errorf("update owner: %v", err)
+		return fmt.Errorf("update owner: %w", err)
 	}
 
 	// Remove redundant collaborators.
 	collaborators, err := repo_model.GetCollaborators(ctx, repo.ID, db.ListOptions{})
 	if err != nil {
-		return fmt.Errorf("getCollaborators: %v", err)
+		return fmt.Errorf("getCollaborators: %w", err)
 	}
 
 	// Dummy object.
@@ -293,7 +293,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 		if c.IsGhost() {
 			collaboration.ID = c.Collaboration.ID
 			if _, err := sess.Delete(collaboration); err != nil {
-				return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
+				return fmt.Errorf("remove collaborator '%d': %w", c.ID, err)
 			}
 			collaboration.ID = 0
 		}
@@ -301,14 +301,14 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 		if c.ID != newOwner.ID {
 			isMember, err := organization.IsOrganizationMember(ctx, newOwner.ID, c.ID)
 			if err != nil {
-				return fmt.Errorf("IsOrgMember: %v", err)
+				return fmt.Errorf("IsOrgMember: %w", err)
 			} else if !isMember {
 				continue
 			}
 		}
 		collaboration.UserID = c.ID
 		if _, err := sess.Delete(collaboration); err != nil {
-			return fmt.Errorf("remove collaborator '%d': %v", c.ID, err)
+			return fmt.Errorf("remove collaborator '%d': %w", c.ID, err)
 		}
 		collaboration.UserID = 0
 	}
@@ -316,42 +316,42 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 	// Remove old team-repository relations.
 	if oldOwner.IsOrganization() {
 		if err := organization.RemoveOrgRepo(ctx, oldOwner.ID, repo.ID); err != nil {
-			return fmt.Errorf("removeOrgRepo: %v", err)
+			return fmt.Errorf("removeOrgRepo: %w", err)
 		}
 	}
 
 	if newOwner.IsOrganization() {
 		teams, err := organization.FindOrgTeams(ctx, newOwner.ID)
 		if err != nil {
-			return fmt.Errorf("LoadTeams: %v", err)
+			return fmt.Errorf("LoadTeams: %w", err)
 		}
 		for _, t := range teams {
 			if t.IncludesAllRepositories {
-				if err := addRepository(ctx, t, repo); err != nil {
-					return fmt.Errorf("addRepository: %v", err)
+				if err := AddRepository(ctx, t, repo); err != nil {
+					return fmt.Errorf("AddRepository: %w", err)
 				}
 			}
 		}
 	} else if err := access_model.RecalculateAccesses(ctx, repo); err != nil {
 		// Organization called this in addRepository method.
-		return fmt.Errorf("recalculateAccesses: %v", err)
+		return fmt.Errorf("recalculateAccesses: %w", err)
 	}
 
 	// Update repository count.
 	if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos+1 WHERE id=?", newOwner.ID); err != nil {
-		return fmt.Errorf("increase new owner repository count: %v", err)
+		return fmt.Errorf("increase new owner repository count: %w", err)
 	} else if _, err := sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", oldOwner.ID); err != nil {
-		return fmt.Errorf("decrease old owner repository count: %v", err)
+		return fmt.Errorf("decrease old owner repository count: %w", err)
 	}
 
 	if err := repo_model.WatchRepo(ctx, doer.ID, repo.ID, true); err != nil {
-		return fmt.Errorf("watchRepo: %v", err)
+		return fmt.Errorf("watchRepo: %w", err)
 	}
 
 	// Remove watch for organization.
 	if oldOwner.IsOrganization() {
 		if err := repo_model.WatchRepo(ctx, oldOwner.ID, repo.ID, false); err != nil {
-			return fmt.Errorf("watchRepo [false]: %v", err)
+			return fmt.Errorf("watchRepo [false]: %w", err)
 		}
 	}
 
@@ -366,7 +366,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 					WHERE
 						issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 		) AS il_too )`, repo.ID, newOwner.ID); err != nil {
-			return fmt.Errorf("Unable to remove old org labels: %v", err)
+			return fmt.Errorf("Unable to remove old org labels: %w", err)
 		}
 
 		if _, err := sess.Exec(`DELETE FROM comment WHERE comment.id IN (
@@ -378,7 +378,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 					WHERE
 						com.type = ? AND issue.repo_id = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != ?))
 		) AS il_too)`, issues_model.CommentTypeLabel, repo.ID, newOwner.ID); err != nil {
-			return fmt.Errorf("Unable to remove old org label comments: %v", err)
+			return fmt.Errorf("Unable to remove old org label comments: %w", err)
 		}
 	}
 
@@ -386,11 +386,11 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 	dir := user_model.UserPath(newOwner.Name)
 
 	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
-		return fmt.Errorf("Failed to create dir %s: %v", dir, err)
+		return fmt.Errorf("Failed to create dir %s: %w", dir, err)
 	}
 
 	if err := util.Rename(repo_model.RepoPath(oldOwner.Name, repo.Name), repo_model.RepoPath(newOwner.Name, repo.Name)); err != nil {
-		return fmt.Errorf("rename repository directory: %v", err)
+		return fmt.Errorf("rename repository directory: %w", err)
 	}
 	repoRenamed = true
 
@@ -402,13 +402,13 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 		return err
 	} else if isExist {
 		if err := util.Rename(wikiPath, repo_model.WikiPath(newOwner.Name, repo.Name)); err != nil {
-			return fmt.Errorf("rename repository wiki: %v", err)
+			return fmt.Errorf("rename repository wiki: %w", err)
 		}
 		wikiRenamed = true
 	}
 
 	if err := deleteRepositoryTransfer(ctx, repo.ID); err != nil {
-		return fmt.Errorf("deleteRepositoryTransfer: %v", err)
+		return fmt.Errorf("deleteRepositoryTransfer: %w", err)
 	}
 	repo.Status = repo_model.RepositoryReady
 	if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
@@ -417,11 +417,11 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 
 	// If there was previously a redirect at this location, remove it.
 	if err := repo_model.DeleteRedirect(ctx, newOwner.ID, repo.Name); err != nil {
-		return fmt.Errorf("delete repo redirect: %v", err)
+		return fmt.Errorf("delete repo redirect: %w", err)
 	}
 
 	if err := repo_model.NewRedirect(ctx, oldOwner.ID, repo.ID, repo.Name, repo.Name); err != nil {
-		return fmt.Errorf("repo_model.NewRedirect: %v", err)
+		return fmt.Errorf("repo_model.NewRedirect: %w", err)
 	}
 
 	return committer.Commit()
diff --git a/models/repo_transfer_test.go b/models/repo_transfer_test.go
index 9125bb8c8dfe6..7904b04e98c3c 100644
--- a/models/repo_transfer_test.go
+++ b/models/repo_transfer_test.go
@@ -17,8 +17,8 @@ import (
 func TestRepositoryTransfer(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
+	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 
 	transfer, err := GetPendingRepositoryTransfer(repo)
 	assert.NoError(t, err)
@@ -32,7 +32,7 @@ func TestRepositoryTransfer(t *testing.T) {
 	assert.Nil(t, transfer)
 	assert.True(t, IsErrNoPendingTransfer(err))
 
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	assert.NoError(t, CreatePendingRepositoryTransfer(doer, user2, repo.ID, nil))
 
@@ -41,7 +41,7 @@ func TestRepositoryTransfer(t *testing.T) {
 	assert.NoError(t, transfer.LoadAttributes())
 	assert.Equal(t, "user2", transfer.Recipient.Name)
 
-	user6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	// Only transfer can be started at any given time
 	err = CreatePendingRepositoryTransfer(doer, user6, repo.ID, nil)
diff --git a/models/appstate/appstate.go b/models/system/appstate.go
similarity index 98%
rename from models/appstate/appstate.go
rename to models/system/appstate.go
index aa5a59e1a3aed..c11a2512aba92 100644
--- a/models/appstate/appstate.go
+++ b/models/system/appstate.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package appstate
+package system
 
 import (
 	"context"
diff --git a/models/system/main_test.go b/models/system/main_test.go
new file mode 100644
index 0000000000000..a56c76aedcd2c
--- /dev/null
+++ b/models/system/main_test.go
@@ -0,0 +1,21 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package system_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models"        // register models
+	_ "code.gitea.io/gitea/models/system" // register models of system
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", ".."),
+	})
+}
diff --git a/models/admin/notice.go b/models/system/notice.go
similarity index 99%
rename from models/admin/notice.go
rename to models/system/notice.go
index 77277e4b2d873..3276fa3ffba42 100644
--- a/models/admin/notice.go
+++ b/models/system/notice.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package admin
+package system
 
 import (
 	"context"
@@ -142,5 +142,5 @@ func DeleteOldSystemNotices(olderThan time.Duration) (err error) {
 	}
 
 	_, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Notice{})
-	return
+	return err
 }
diff --git a/models/system/notice_test.go b/models/system/notice_test.go
new file mode 100644
index 0000000000000..768bcca66cdd6
--- /dev/null
+++ b/models/system/notice_test.go
@@ -0,0 +1,117 @@
+// 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 system_test
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/system"
+	"code.gitea.io/gitea/models/unittest"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNotice_TrStr(t *testing.T) {
+	notice := &system.Notice{
+		Type:        system.NoticeRepository,
+		Description: "test description",
+	}
+	assert.Equal(t, "admin.notices.type_1", notice.TrStr())
+}
+
+func TestCreateNotice(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	noticeBean := &system.Notice{
+		Type:        system.NoticeRepository,
+		Description: "test description",
+	}
+	unittest.AssertNotExistsBean(t, noticeBean)
+	assert.NoError(t, system.CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description))
+	unittest.AssertExistsAndLoadBean(t, noticeBean)
+}
+
+func TestCreateRepositoryNotice(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	noticeBean := &system.Notice{
+		Type:        system.NoticeRepository,
+		Description: "test description",
+	}
+	unittest.AssertNotExistsBean(t, noticeBean)
+	assert.NoError(t, system.CreateRepositoryNotice(noticeBean.Description))
+	unittest.AssertExistsAndLoadBean(t, noticeBean)
+}
+
+// TODO TestRemoveAllWithNotice
+
+func TestCountNotices(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	assert.Equal(t, int64(3), system.CountNotices())
+}
+
+func TestNotices(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	notices, err := system.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 = system.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, &system.Notice{ID: 3})
+	assert.NoError(t, system.DeleteNotice(3))
+	unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNotices(t *testing.T) {
+	// delete a non-empty range
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+	assert.NoError(t, system.DeleteNotices(1, 2))
+	unittest.AssertNotExistsBean(t, &system.Notice{ID: 1})
+	unittest.AssertNotExistsBean(t, &system.Notice{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNotices2(t *testing.T) {
+	// delete an empty range
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+	assert.NoError(t, system.DeleteNotices(3, 2))
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNoticesByIDs(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+	assert.NoError(t, system.DeleteNoticesByIDs([]int64{1, 3}))
+	unittest.AssertNotExistsBean(t, &system.Notice{ID: 1})
+	unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+	unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
+}
diff --git a/models/system/setting.go b/models/system/setting.go
new file mode 100644
index 0000000000000..b4011b1b3ed62
--- /dev/null
+++ b/models/system/setting.go
@@ -0,0 +1,289 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package system
+
+import (
+	"context"
+	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/cache"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"strk.kbt.io/projects/go/libravatar"
+	"xorm.io/builder"
+)
+
+// Setting is a key value store of user settings
+type Setting 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"`
+}
+
+// TableName sets the table name for the settings struct
+func (s *Setting) TableName() string {
+	return "system_setting"
+}
+
+func (s *Setting) GetValueBool() bool {
+	if s == nil {
+		return false
+	}
+
+	b, _ := strconv.ParseBool(s.SettingValue)
+	return b
+}
+
+func init() {
+	db.RegisterModel(new(Setting))
+}
+
+// ErrSettingIsNotExist represents an error that a setting is not exist with special key
+type ErrSettingIsNotExist struct {
+	Key string
+}
+
+// Error implements error
+func (err ErrSettingIsNotExist) Error() string {
+	return fmt.Sprintf("System setting[%s] is not exist", err.Key)
+}
+
+// IsErrSettingIsNotExist return true if err is ErrSettingIsNotExist
+func IsErrSettingIsNotExist(err error) bool {
+	_, ok := err.(ErrSettingIsNotExist)
+	return ok
+}
+
+// ErrDataExpired represents an error that update a record which has been updated by another thread
+type ErrDataExpired struct {
+	Key string
+}
+
+// Error implements error
+func (err ErrDataExpired) Error() string {
+	return fmt.Sprintf("System setting[%s] has been updated by another thread", err.Key)
+}
+
+// IsErrDataExpired return true if err is ErrDataExpired
+func IsErrDataExpired(err error) bool {
+	_, ok := err.(ErrDataExpired)
+	return ok
+}
+
+// GetSettingNoCache returns specific setting without using the cache
+func GetSettingNoCache(key string) (*Setting, error) {
+	v, err := GetSettings([]string{key})
+	if err != nil {
+		return nil, err
+	}
+	if len(v) == 0 {
+		return nil, ErrSettingIsNotExist{key}
+	}
+	return v[key], nil
+}
+
+// GetSetting returns the setting value via the key
+func GetSetting(key string) (*Setting, error) {
+	return cache.Get(genSettingCacheKey(key), func() (*Setting, error) {
+		res, err := GetSettingNoCache(key)
+		if err != nil {
+			return nil, err
+		}
+		return res, nil
+	})
+}
+
+// GetSettingBool return bool value of setting,
+// none existing keys and errors are ignored and result in false
+func GetSettingBool(key string) bool {
+	s, _ := GetSetting(key)
+	return s.GetValueBool()
+}
+
+// GetSettings returns specific settings
+func GetSettings(keys []string) (map[string]*Setting, error) {
+	for i := 0; i < len(keys); i++ {
+		keys[i] = strings.ToLower(keys[i])
+	}
+	settings := make([]*Setting, 0, len(keys))
+	if err := db.GetEngine(db.DefaultContext).
+		Where(builder.In("setting_key", keys)).
+		Find(&settings); err != nil {
+		return nil, err
+	}
+	settingsMap := make(map[string]*Setting)
+	for _, s := range settings {
+		settingsMap[s.SettingKey] = s
+	}
+	return settingsMap, nil
+}
+
+type AllSettings map[string]*Setting
+
+func (settings AllSettings) Get(key string) Setting {
+	if v, ok := settings[key]; ok {
+		return *v
+	}
+	return Setting{}
+}
+
+func (settings AllSettings) GetBool(key string) bool {
+	b, _ := strconv.ParseBool(settings.Get(key).SettingValue)
+	return b
+}
+
+func (settings AllSettings) GetVersion(key string) int {
+	return settings.Get(key).Version
+}
+
+// GetAllSettings returns all settings from user
+func GetAllSettings() (AllSettings, error) {
+	settings := make([]*Setting, 0, 5)
+	if err := db.GetEngine(db.DefaultContext).
+		Find(&settings); err != nil {
+		return nil, err
+	}
+	settingsMap := make(map[string]*Setting)
+	for _, s := range settings {
+		settingsMap[s.SettingKey] = s
+	}
+	return settingsMap, nil
+}
+
+// DeleteSetting deletes a specific setting for a user
+func DeleteSetting(setting *Setting) error {
+	cache.Remove(genSettingCacheKey(setting.SettingKey))
+	_, err := db.GetEngine(db.DefaultContext).Delete(setting)
+	return err
+}
+
+func SetSettingNoVersion(key, value string) error {
+	s, err := GetSettingNoCache(key)
+	if IsErrSettingIsNotExist(err) {
+		return SetSetting(&Setting{
+			SettingKey:   key,
+			SettingValue: value,
+		})
+	}
+	if err != nil {
+		return err
+	}
+	s.SettingValue = value
+	return SetSetting(s)
+}
+
+// SetSetting updates a users' setting for a specific key
+func SetSetting(setting *Setting) error {
+	_, err := cache.Set(genSettingCacheKey(setting.SettingKey), func() (*Setting, error) {
+		return setting, upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version)
+	})
+	if err != nil {
+		return err
+	}
+
+	setting.Version++
+	return nil
+}
+
+func upsertSettingValue(key, value string, version int) error {
+	return db.WithTx(func(ctx context.Context) error {
+		e := db.GetEngine(ctx)
+
+		// here we use a general method to do a safe upsert for different databases (and most transaction levels)
+		// 1. try to UPDATE the record and acquire the transaction write lock
+		//    if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly
+		//    if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist  (b) value is not changed
+		// 2. do a SELECT to check if the row exists or not (we already have the transaction lock)
+		// 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe)
+		//
+		// to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1`
+		//    to make sure the UPDATE always returns a non-zero value for existing (unchanged) records.
+
+		res, err := e.Exec("UPDATE system_setting SET setting_value=?, version = version+1 WHERE setting_key=? AND version=?", value, key, version)
+		if err != nil {
+			return err
+		}
+		rows, _ := res.RowsAffected()
+		if rows > 0 {
+			// the existing row is updated, so we can return
+			return nil
+		}
+
+		// in case the value isn't changed, update would return 0 rows changed, so we need this check
+		has, err := e.Exist(&Setting{SettingKey: key})
+		if err != nil {
+			return err
+		}
+		if has {
+			return ErrDataExpired{Key: key}
+		}
+
+		// if no existing row, insert a new row
+		_, err = e.Insert(&Setting{SettingKey: key, SettingValue: value})
+		return err
+	})
+}
+
+var (
+	GravatarSourceURL *url.URL
+	LibravatarService *libravatar.Libravatar
+)
+
+func Init() error {
+	var disableGravatar bool
+	disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
+	if IsErrSettingIsNotExist(err) {
+		disableGravatar = setting.GetDefaultDisableGravatar()
+		disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
+	} else if err != nil {
+		return err
+	} else {
+		disableGravatar = disableGravatarSetting.GetValueBool()
+	}
+
+	var enableFederatedAvatar bool
+	enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
+	if IsErrSettingIsNotExist(err) {
+		enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
+		enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
+	} else if err != nil {
+		return err
+	} else {
+		enableFederatedAvatar = disableGravatarSetting.GetValueBool()
+	}
+
+	if setting.OfflineMode {
+		disableGravatar = true
+		enableFederatedAvatar = false
+	}
+
+	if disableGravatar || !enableFederatedAvatar {
+		var err error
+		GravatarSourceURL, err = url.Parse(setting.GravatarSource)
+		if err != nil {
+			return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting.GravatarSource, err)
+		}
+	}
+
+	if enableFederatedAvatarSetting.GetValueBool() {
+		LibravatarService = libravatar.New()
+		if GravatarSourceURL.Scheme == "https" {
+			LibravatarService.SetUseHTTPS(true)
+			LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
+		} else {
+			LibravatarService.SetUseHTTPS(false)
+			LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
+		}
+	}
+	return nil
+}
diff --git a/models/system/setting_key.go b/models/system/setting_key.go
new file mode 100644
index 0000000000000..14105b89d0d3b
--- /dev/null
+++ b/models/system/setting_key.go
@@ -0,0 +1,16 @@
+// 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 system
+
+// enumerate all system setting keys
+const (
+	KeyPictureDisableGravatar       = "picture.disable_gravatar"
+	KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar"
+)
+
+// genSettingCacheKey returns the cache key for some configuration
+func genSettingCacheKey(key string) string {
+	return "system.setting." + key
+}
diff --git a/models/system/setting_test.go b/models/system/setting_test.go
new file mode 100644
index 0000000000000..d25fc05f31d1b
--- /dev/null
+++ b/models/system/setting_test.go
@@ -0,0 +1,53 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package system_test
+
+import (
+	"strings"
+	"testing"
+
+	"code.gitea.io/gitea/models/system"
+	"code.gitea.io/gitea/models/unittest"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSettings(t *testing.T) {
+	keyName := "server.LFS_LOCKS_PAGING_NUM"
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	newSetting := &system.Setting{SettingKey: keyName, SettingValue: "50"}
+
+	// create setting
+	err := system.SetSetting(newSetting)
+	assert.NoError(t, err)
+	// test about saving unchanged values
+	err = system.SetSetting(newSetting)
+	assert.NoError(t, err)
+
+	// get specific setting
+	settings, err := system.GetSettings([]string{keyName})
+	assert.NoError(t, err)
+	assert.Len(t, settings, 1)
+	assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
+
+	// updated setting
+	updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.Version}
+	err = system.SetSetting(updatedSetting)
+	assert.NoError(t, err)
+
+	// get all settings
+	settings, err = system.GetAllSettings()
+	assert.NoError(t, err)
+	assert.Len(t, settings, 3)
+	assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
+
+	// delete setting
+	err = system.DeleteSetting(&system.Setting{SettingKey: strings.ToLower(keyName)})
+	assert.NoError(t, err)
+	settings, err = system.GetAllSettings()
+	assert.NoError(t, err)
+	assert.Len(t, settings, 2)
+}
diff --git a/models/unit/unit.go b/models/unit/unit.go
index e94775413e4e7..b83bd61831a44 100644
--- a/models/unit/unit.go
+++ b/models/unit/unit.go
@@ -318,7 +318,7 @@ func FindUnitTypes(nameKeys ...string) (res []Type) {
 			res = append(res, TypeInvalid)
 		}
 	}
-	return
+	return res
 }
 
 // TypeFromKey give the unit key name and return unit
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index baea46dbce68f..2e6c25ae48f5e 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -7,12 +7,12 @@ package unittest
 import (
 	"context"
 	"fmt"
-	"net/url"
 	"os"
 	"path/filepath"
 	"testing"
 
 	"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/git"
 	"code.gitea.io/gitea/modules/setting"
@@ -91,10 +91,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
 	setting.AppDataPath = appDataPath
 	setting.AppWorkPath = testOpts.GiteaRootPath
 	setting.StaticRootPath = testOpts.GiteaRootPath
-	setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/")
-	if err != nil {
-		fatalTestError("url.Parse: %v\n", err)
-	}
+	setting.GravatarSource = "https://secure.gravatar.com/avatar/"
+
 	setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
 
 	setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
@@ -107,22 +105,25 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
 
 	setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages")
 
+	setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home")
+
 	if err = storage.Init(); err != nil {
 		fatalTestError("storage.Init: %v\n", err)
 	}
+	if err = system_model.Init(); err != nil {
+		fatalTestError("models.Init: %v\n", err)
+	}
 
 	if err = util.RemoveAll(repoRootPath); err != nil {
 		fatalTestError("util.RemoveAll: %v\n", err)
 	}
-	if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
+	if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
 		fatalTestError("util.CopyDir: %v\n", err)
 	}
 
-	if err = git.InitOnceWithSync(context.Background()); err != nil {
+	if err = git.InitFull(context.Background()); err != nil {
 		fatalTestError("git.Init: %v\n", err)
 	}
-	git.CheckLFSVersion()
-
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	if err != nil {
 		fatalTestError("unable to read the new repo root: %v\n", err)
@@ -202,10 +203,8 @@ func PrepareTestDatabase() error {
 func PrepareTestEnv(t testing.TB) {
 	assert.NoError(t, PrepareTestDatabase())
 	assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
-	metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
+	metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
 	assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
-	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
-
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	assert.NoError(t, err)
 	for _, ownerDir := range ownerDirs {
diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go
index 6c20c2781bb4d..c8673debed02b 100644
--- a/models/unittest/unit_tests.go
+++ b/models/unittest/unit_tests.go
@@ -55,7 +55,7 @@ func BeanExists(t assert.TestingT, bean interface{}, conditions ...interface{})
 }
 
 // AssertExistsAndLoadBean assert that a bean exists and load it from the test database
-func AssertExistsAndLoadBean(t assert.TestingT, bean interface{}, conditions ...interface{}) interface{} {
+func AssertExistsAndLoadBean[T any](t assert.TestingT, bean T, conditions ...interface{}) T {
 	exists, err := LoadBeanIfExists(bean, conditions...)
 	assert.NoError(t, err)
 	assert.True(t, exists,
diff --git a/models/user.go b/models/user.go
index 49374014aa7db..0fc28ff055a7a 100644
--- a/models/user.go
+++ b/models/user.go
@@ -12,6 +12,7 @@ import (
 
 	_ "image/jpeg" // Needed for jpeg support
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
@@ -27,17 +28,17 @@ import (
 )
 
 // DeleteUser deletes models associated to an user.
-func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
+func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) {
 	e := db.GetEngine(ctx)
 
 	// ***** START: Watch *****
 	watchedRepoIDs := make([]int64, 0, 10)
 	if err = e.Table("watch").Cols("watch.repo_id").
 		Where("watch.user_id = ?", u.ID).And("watch.mode <>?", repo_model.WatchModeDont).Find(&watchedRepoIDs); err != nil {
-		return fmt.Errorf("get all watches: %v", err)
+		return fmt.Errorf("get all watches: %w", err)
 	}
 	if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(repo_model.Repository)); err != nil {
-		return fmt.Errorf("decrease repository num_watches: %v", err)
+		return fmt.Errorf("decrease repository num_watches: %w", err)
 	}
 	// ***** END: Watch *****
 
@@ -45,9 +46,9 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 	starredRepoIDs := make([]int64, 0, 10)
 	if err = e.Table("star").Cols("star.repo_id").
 		Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
-		return fmt.Errorf("get all stars: %v", err)
+		return fmt.Errorf("get all stars: %w", err)
 	} else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).NoAutoTime().Update(new(repo_model.Repository)); err != nil {
-		return fmt.Errorf("decrease repository num_stars: %v", err)
+		return fmt.Errorf("decrease repository num_stars: %w", err)
 	}
 	// ***** END: Star *****
 
@@ -55,29 +56,29 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 	followeeIDs := make([]int64, 0, 10)
 	if err = e.Table("follow").Cols("follow.follow_id").
 		Where("follow.user_id = ?", u.ID).Find(&followeeIDs); err != nil {
-		return fmt.Errorf("get all followees: %v", err)
+		return fmt.Errorf("get all followees: %w", err)
 	} else if _, err = e.Decr("num_followers").In("id", followeeIDs).Update(new(user_model.User)); err != nil {
-		return fmt.Errorf("decrease user num_followers: %v", err)
+		return fmt.Errorf("decrease user num_followers: %w", err)
 	}
 
 	followerIDs := make([]int64, 0, 10)
 	if err = e.Table("follow").Cols("follow.user_id").
 		Where("follow.follow_id = ?", u.ID).Find(&followerIDs); err != nil {
-		return fmt.Errorf("get all followers: %v", err)
+		return fmt.Errorf("get all followers: %w", err)
 	} else if _, err = e.Decr("num_following").In("id", followerIDs).Update(new(user_model.User)); err != nil {
-		return fmt.Errorf("decrease user num_following: %v", err)
+		return fmt.Errorf("decrease user num_following: %w", err)
 	}
 	// ***** END: Follow *****
 
 	if err = db.DeleteBeans(ctx,
-		&AccessToken{UID: u.ID},
+		&auth_model.AccessToken{UID: u.ID},
 		&repo_model.Collaboration{UserID: u.ID},
 		&access_model.Access{UserID: u.ID},
 		&repo_model.Watch{UserID: u.ID},
 		&repo_model.Star{UID: u.ID},
 		&user_model.Follow{UserID: u.ID},
 		&user_model.Follow{FollowID: u.ID},
-		&Action{UserID: u.ID},
+		&activities_model.Action{UserID: u.ID},
 		&issues_model.IssueUser{UID: u.ID},
 		&user_model.EmailAddress{UID: u.ID},
 		&user_model.UserOpenID{UID: u.ID},
@@ -85,24 +86,26 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 		&organization.TeamUser{UID: u.ID},
 		&issues_model.Stopwatch{UserID: u.ID},
 		&user_model.Setting{UserID: u.ID},
+		&user_model.UserBadge{UserID: u.ID},
 		&pull_model.AutoMerge{DoerID: u.ID},
 		&pull_model.ReviewState{UserID: u.ID},
+		&user_model.Redirect{RedirectUserID: u.ID},
 	); err != nil {
-		return fmt.Errorf("deleteBeans: %v", err)
+		return fmt.Errorf("deleteBeans: %w", err)
 	}
 
 	if err := auth_model.DeleteOAuth2RelictsByUserID(ctx, u.ID); err != nil {
 		return err
 	}
 
-	if setting.Service.UserDeleteWithCommentsMaxTime != 0 &&
-		u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now()) {
+	if purge || (setting.Service.UserDeleteWithCommentsMaxTime != 0 &&
+		u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())) {
 
 		// Delete Comments
 		const batchSize = 50
-		for start := 0; ; start += batchSize {
+		for {
 			comments := make([]*issues_model.Comment, 0, batchSize)
-			if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, start).Find(&comments); err != nil {
+			if err = e.Where("type=? AND poster_id=?", issues_model.CommentTypeComment, u.ID).Limit(batchSize, 0).Find(&comments); err != nil {
 				return err
 			}
 			if len(comments) == 0 {
@@ -133,7 +136,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 			// Also, as we didn't update branch protections when removing entries from `access` table,
 			//   it's safer to iterate all protected branches.
 			if err = e.Limit(batchSize, start).Find(&protections); err != nil {
-				return fmt.Errorf("findProtectedBranches: %v", err)
+				return fmt.Errorf("findProtectedBranches: %w", err)
 			}
 			if len(protections) == 0 {
 				break
@@ -158,7 +161,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 						"merge_whitelist_user_i_ds",
 						"approvals_whitelist_user_i_ds",
 					).Update(p); err != nil {
-						return fmt.Errorf("updateProtectedBranches: %v", err)
+						return fmt.Errorf("updateProtectedBranches: %w", err)
 					}
 				}
 			}
@@ -168,39 +171,39 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 
 	// ***** START: PublicKey *****
 	if _, err = db.DeleteByBean(ctx, &asymkey_model.PublicKey{OwnerID: u.ID}); err != nil {
-		return fmt.Errorf("deletePublicKeys: %v", err)
+		return fmt.Errorf("deletePublicKeys: %w", err)
 	}
 	// ***** END: PublicKey *****
 
 	// ***** START: GPGPublicKey *****
 	keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
 	if err != nil {
-		return fmt.Errorf("ListGPGKeys: %v", err)
+		return fmt.Errorf("ListGPGKeys: %w", err)
 	}
 	// Delete GPGKeyImport(s).
 	for _, key := range keys {
 		if _, err = db.DeleteByBean(ctx, &asymkey_model.GPGKeyImport{KeyID: key.KeyID}); err != nil {
-			return fmt.Errorf("deleteGPGKeyImports: %v", err)
+			return fmt.Errorf("deleteGPGKeyImports: %w", err)
 		}
 	}
 	if _, err = db.DeleteByBean(ctx, &asymkey_model.GPGKey{OwnerID: u.ID}); err != nil {
-		return fmt.Errorf("deleteGPGKeys: %v", err)
+		return fmt.Errorf("deleteGPGKeys: %w", err)
 	}
 	// ***** END: GPGPublicKey *****
 
 	// Clear assignee.
 	if _, err = db.DeleteByBean(ctx, &issues_model.IssueAssignees{AssigneeID: u.ID}); err != nil {
-		return fmt.Errorf("clear assignee: %v", err)
+		return fmt.Errorf("clear assignee: %w", err)
 	}
 
 	// ***** START: ExternalLoginUser *****
 	if err = user_model.RemoveAllAccountLinks(ctx, u); err != nil {
-		return fmt.Errorf("ExternalLoginUser: %v", err)
+		return fmt.Errorf("ExternalLoginUser: %w", err)
 	}
 	// ***** END: ExternalLoginUser *****
 
 	if _, err = e.ID(u.ID).Delete(new(user_model.User)); err != nil {
-		return fmt.Errorf("Delete: %v", err)
+		return fmt.Errorf("delete: %w", err)
 	}
 
 	return nil
diff --git a/models/user/avatar.go b/models/user/avatar.go
index 6a44a3bcb3c32..102206f3a208e 100644
--- a/models/user/avatar.go
+++ b/models/user/avatar.go
@@ -14,6 +14,7 @@ import (
 
 	"code.gitea.io/gitea/models/avatars"
 	"code.gitea.io/gitea/models/db"
+	system_model "code.gitea.io/gitea/models/system"
 	"code.gitea.io/gitea/modules/avatar"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -34,7 +35,7 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
 
 	img, err := avatar.RandomImage([]byte(seed))
 	if err != nil {
-		return fmt.Errorf("RandomImage: %v", err)
+		return fmt.Errorf("RandomImage: %w", err)
 	}
 
 	u.Avatar = avatars.HashEmail(seed)
@@ -46,7 +47,7 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
 		}
 		return err
 	}); err != nil {
-		return fmt.Errorf("Failed to create dir %s: %v", u.CustomAvatarRelativePath(), err)
+		return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
 	}
 
 	if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
@@ -67,10 +68,14 @@ func (u *User) AvatarLinkWithSize(size int) string {
 	useLocalAvatar := false
 	autoGenerateAvatar := false
 
+	disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
+
+	disableGravatar := disableGravatarSetting.GetValueBool()
+
 	switch {
 	case u.UseCustomAvatar:
 		useLocalAvatar = true
-	case setting.DisableGravatar, setting.OfflineMode:
+	case disableGravatar, setting.OfflineMode:
 		useLocalAvatar = true
 		autoGenerateAvatar = true
 	}
diff --git a/models/user/badge.go b/models/user/badge.go
new file mode 100644
index 0000000000000..5ff840cb8c35e
--- /dev/null
+++ b/models/user/badge.go
@@ -0,0 +1,42 @@
+// 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 user
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/models/db"
+)
+
+// Badge represents a user badge
+type Badge struct {
+	ID          int64 `xorm:"pk autoincr"`
+	Description string
+	ImageURL    string
+}
+
+// UserBadge represents a user badge
+type UserBadge struct {
+	ID      int64 `xorm:"pk autoincr"`
+	BadgeID int64
+	UserID  int64 `xorm:"INDEX"`
+}
+
+func init() {
+	db.RegisterModel(new(Badge))
+	db.RegisterModel(new(UserBadge))
+}
+
+// GetUserBadges returns the user's badges.
+func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) {
+	sess := db.GetEngine(ctx).
+		Select("`badge`.*").
+		Join("INNER", "user_badge", "`user_badge`.badge_id=badge.id").
+		Where("user_badge.user_id=?", u.ID)
+
+	badges := make([]*Badge, 0, 8)
+	count, err := sess.FindAndCount(&badges)
+	return badges, count, err
+}
diff --git a/models/user/email_address.go b/models/user/email_address.go
index c931db9c16720..d6279b6639ab7 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -40,7 +40,12 @@ func (err ErrEmailCharIsNotSupported) Error() string {
 	return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
 }
 
+func (err ErrEmailCharIsNotSupported) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
+// or has a leading '-' character
 type ErrEmailInvalid struct {
 	Email string
 }
@@ -55,6 +60,10 @@ func (err ErrEmailInvalid) Error() string {
 	return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
 }
 
+func (err ErrEmailInvalid) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
 type ErrEmailAlreadyUsed struct {
 	Email string
@@ -70,6 +79,10 @@ func (err ErrEmailAlreadyUsed) Error() string {
 	return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
 }
 
+func (err ErrEmailAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrEmailAddressNotExist email address not exist
 type ErrEmailAddressNotExist struct {
 	Email string
@@ -85,6 +98,10 @@ func (err ErrEmailAddressNotExist) Error() string {
 	return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
 }
 
+func (err ErrEmailAddressNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrPrimaryEmailCannotDelete primary email address cannot be deleted
 type ErrPrimaryEmailCannotDelete struct {
 	Email string
@@ -100,6 +117,10 @@ func (err ErrPrimaryEmailCannotDelete) Error() string {
 	return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
 }
 
+func (err ErrPrimaryEmailCannotDelete) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // EmailAddress is the list of all email addresses of a user. It also contains the
 // primary email address which is saved in user table.
 type EmailAddress struct {
@@ -134,9 +155,7 @@ func ValidateEmail(email string) error {
 		return ErrEmailCharIsNotSupported{email}
 	}
 
-	if !(email[0] >= 'a' && email[0] <= 'z') &&
-		!(email[0] >= 'A' && email[0] <= 'Z') &&
-		!(email[0] >= '0' && email[0] <= '9') {
+	if email[0] == '-' {
 		return ErrEmailInvalid{email}
 	}
 
@@ -245,7 +264,7 @@ func AddEmailAddresses(emails []*EmailAddress) error {
 	}
 
 	if err := db.Insert(db.DefaultContext, emails); err != nil {
-		return fmt.Errorf("Insert: %v", err)
+		return fmt.Errorf("Insert: %w", err)
 	}
 
 	return nil
@@ -466,7 +485,7 @@ func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error)
 	count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid").
 		Where(cond).Count(new(EmailAddress))
 	if err != nil {
-		return nil, 0, fmt.Errorf("Count: %v", err)
+		return nil, 0, fmt.Errorf("Count: %w", err)
 	}
 
 	orderby := opts.SortType.String()
@@ -511,7 +530,7 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
 	}
 	if activate {
 		if used, err := IsEmailActive(ctx, email, addr.ID); err != nil {
-			return fmt.Errorf("unable to check isEmailActive() for %s: %v", email, err)
+			return fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err)
 		} else if used {
 			return ErrEmailAlreadyUsed{Email: email}
 		}
@@ -532,10 +551,10 @@ func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
 		if user.IsActive != activate {
 			user.IsActive = activate
 			if user.Rands, err = GetUserSalt(); err != nil {
-				return fmt.Errorf("unable to generate salt: %v", err)
+				return fmt.Errorf("unable to generate salt: %w", err)
 			}
 			if err = UpdateUserCols(ctx, &user, "is_active", "rands"); err != nil {
-				return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
+				return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err)
 			}
 		}
 	}
diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go
index 471598c897640..b9acaa1113aee 100644
--- a/models/user/email_address_test.go
+++ b/models/user/email_address_test.go
@@ -281,23 +281,25 @@ func TestEmailAddressValidate(t *testing.T) {
 		`first~last@iana.org`:            nil,
 		`first;last@iana.org`:            user_model.ErrEmailCharIsNotSupported{`first;last@iana.org`},
 		".233@qq.com":                    user_model.ErrEmailInvalid{".233@qq.com"},
-		"!233@qq.com":                    user_model.ErrEmailInvalid{"!233@qq.com"},
-		"#233@qq.com":                    user_model.ErrEmailInvalid{"#233@qq.com"},
-		"$233@qq.com":                    user_model.ErrEmailInvalid{"$233@qq.com"},
-		"%233@qq.com":                    user_model.ErrEmailInvalid{"%233@qq.com"},
-		"&233@qq.com":                    user_model.ErrEmailInvalid{"&233@qq.com"},
-		"'233@qq.com":                    user_model.ErrEmailInvalid{"'233@qq.com"},
-		"*233@qq.com":                    user_model.ErrEmailInvalid{"*233@qq.com"},
-		"+233@qq.com":                    user_model.ErrEmailInvalid{"+233@qq.com"},
-		"/233@qq.com":                    user_model.ErrEmailInvalid{"/233@qq.com"},
-		"=233@qq.com":                    user_model.ErrEmailInvalid{"=233@qq.com"},
-		"?233@qq.com":                    user_model.ErrEmailInvalid{"?233@qq.com"},
-		"^233@qq.com":                    user_model.ErrEmailInvalid{"^233@qq.com"},
-		"`233@qq.com":                    user_model.ErrEmailInvalid{"`233@qq.com"},
-		"{233@qq.com":                    user_model.ErrEmailInvalid{"{233@qq.com"},
-		"|233@qq.com":                    user_model.ErrEmailInvalid{"|233@qq.com"},
-		"}233@qq.com":                    user_model.ErrEmailInvalid{"}233@qq.com"},
-		"~233@qq.com":                    user_model.ErrEmailInvalid{"~233@qq.com"},
+		"!233@qq.com":                    nil,
+		"#233@qq.com":                    nil,
+		"$233@qq.com":                    nil,
+		"%233@qq.com":                    nil,
+		"&233@qq.com":                    nil,
+		"'233@qq.com":                    nil,
+		"*233@qq.com":                    nil,
+		"+233@qq.com":                    nil,
+		"-233@qq.com":                    user_model.ErrEmailInvalid{"-233@qq.com"},
+		"/233@qq.com":                    nil,
+		"=233@qq.com":                    nil,
+		"?233@qq.com":                    nil,
+		"^233@qq.com":                    nil,
+		"_233@qq.com":                    nil,
+		"`233@qq.com":                    nil,
+		"{233@qq.com":                    nil,
+		"|233@qq.com":                    nil,
+		"}233@qq.com":                    nil,
+		"~233@qq.com":                    nil,
 		";233@qq.com":                    user_model.ErrEmailCharIsNotSupported{";233@qq.com"},
 		"Foo ":              user_model.ErrEmailCharIsNotSupported{"Foo "},
 		string([]byte{0xE2, 0x84, 0xAA}): user_model.ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
diff --git a/models/user/error.go b/models/user/error.go
index 25e0d8ea8a428..3fe4ee6657ecf 100644
--- a/models/user/error.go
+++ b/models/user/error.go
@@ -6,6 +6,8 @@ package user
 
 import (
 	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 //  ____ ___
@@ -30,6 +32,11 @@ func (err ErrUserAlreadyExist) Error() string {
 	return fmt.Sprintf("user already exists [name: %s]", err.Name)
 }
 
+// Unwrap unwraps this error as a ErrExist error
+func (err ErrUserAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrUserNotExist represents a "UserNotExist" kind of error.
 type ErrUserNotExist struct {
 	UID   int64
@@ -47,6 +54,11 @@ func (err ErrUserNotExist) Error() string {
 	return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
 }
 
+// Unwrap unwraps this error as a ErrNotExist error
+func (err ErrUserNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
 type ErrUserProhibitLogin struct {
 	UID  int64
@@ -63,6 +75,11 @@ func (err ErrUserProhibitLogin) Error() string {
 	return fmt.Sprintf("user is not allowed login [uid: %d, name: %s]", err.UID, err.Name)
 }
 
+// Unwrap unwraps this error as a ErrPermission error
+func (err ErrUserProhibitLogin) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrUserInactive represents a "ErrUserInactive" kind of error.
 type ErrUserInactive struct {
 	UID  int64
@@ -78,3 +95,8 @@ func IsErrUserInactive(err error) bool {
 func (err ErrUserInactive) Error() string {
 	return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
 }
+
+// Unwrap unwraps this error as a ErrPermission error
+func (err ErrUserInactive) Unwrap() error {
+	return util.ErrPermissionDenied
+}
diff --git a/models/user/external_login_user.go b/models/user/external_login_user.go
index 422823b89c3ff..496717c57bcf6 100644
--- a/models/user/external_login_user.go
+++ b/models/user/external_login_user.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -31,6 +32,10 @@ func (err ErrExternalLoginUserAlreadyExist) Error() string {
 	return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
 }
 
+func (err ErrExternalLoginUserAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
 type ErrExternalLoginUserNotExist struct {
 	UserID        int64
@@ -47,6 +52,10 @@ func (err ErrExternalLoginUserNotExist) Error() string {
 	return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 }
 
+func (err ErrExternalLoginUserNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ExternalLoginUser makes the connecting between some existing user and additional external login sources
 type ExternalLoginUser struct {
 	ExternalID        string                 `xorm:"pk NOT NULL"`
diff --git a/models/user/follow.go b/models/user/follow.go
index 6b02486c438c7..5f24f706d16b5 100644
--- a/models/user/follow.go
+++ b/models/user/follow.go
@@ -9,7 +9,7 @@ import (
 	"code.gitea.io/gitea/modules/timeutil"
 )
 
-// Follow represents relations of user and his/her followers.
+// Follow represents relations of user and their followers.
 type Follow struct {
 	ID          int64              `xorm:"pk autoincr"`
 	UserID      int64              `xorm:"UNIQUE(follow)"`
diff --git a/models/user/list.go b/models/user/list.go
index 68e62ca15d3df..6c43c961c8da0 100644
--- a/models/user/list.go
+++ b/models/user/list.go
@@ -55,7 +55,7 @@ func (users UserList) loadTwoFactorStatus(ctx context.Context) (map[int64]*auth.
 	userIDs := users.GetUserIDs()
 	tokenMaps := make(map[int64]*auth.TwoFactor, len(userIDs))
 	if err := db.GetEngine(ctx).In("uid", userIDs).Find(&tokenMaps); err != nil {
-		return nil, fmt.Errorf("find two factor: %v", err)
+		return nil, fmt.Errorf("find two factor: %w", err)
 	}
 	return tokenMaps, nil
 }
@@ -66,7 +66,7 @@ func (users UserList) userIDsWithWebAuthn(ctx context.Context) ([]int64, error)
 	}
 	ids := make([]int64, 0, len(users))
 	if err := db.GetEngine(ctx).Table(new(auth.WebAuthnCredential)).In("user_id", users.GetUserIDs()).Select("user_id").Distinct("user_id").Find(&ids); err != nil {
-		return nil, fmt.Errorf("find two factor: %v", err)
+		return nil, fmt.Errorf("find two factor: %w", err)
 	}
 	return ids, nil
 }
diff --git a/models/user/openid.go b/models/user/openid.go
index 8ef0ce5ed7ee3..f8e8a787e6eea 100644
--- a/models/user/openid.go
+++ b/models/user/openid.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrOpenIDNotExist openid is not known
@@ -65,6 +66,10 @@ func (err ErrOpenIDAlreadyUsed) Error() string {
 	return fmt.Sprintf("OpenID already in use [oid: %s]", err.OpenID)
 }
 
+func (err ErrOpenIDAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
 // NOTE: make sure openid.URI is normalized already
 func AddUserOpenID(ctx context.Context, openid *UserOpenID) error {
diff --git a/models/user/redirect.go b/models/user/redirect.go
index 49370218dbd16..af8d6439ad995 100644
--- a/models/user/redirect.go
+++ b/models/user/redirect.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
@@ -27,6 +28,10 @@ func (err ErrUserRedirectNotExist) Error() string {
 	return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
 }
 
+func (err ErrUserRedirectNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Redirect represents that a user name should be redirected to another
 type Redirect struct {
 	ID             int64  `xorm:"pk autoincr"`
diff --git a/models/user/search.go b/models/user/search.go
index a81cee1c22ab3..fa4a021a473ec 100644
--- a/models/user/search.go
+++ b/models/user/search.go
@@ -9,7 +9,6 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
 
@@ -58,31 +57,7 @@ func (opts *SearchUserOptions) toSearchQueryBase() *xorm.Session {
 		cond = cond.And(builder.In("visibility", opts.Visible))
 	}
 
-	if opts.Actor != nil {
-		var exprCond builder.Cond = builder.Expr("org_user.org_id = `user`.id")
-
-		// If Admin - they see all users!
-		if !opts.Actor.IsAdmin {
-			// Force visibility for privacy
-			var accessCond builder.Cond
-			if !opts.Actor.IsRestricted {
-				accessCond = builder.Or(
-					builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
-					builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
-			} else {
-				// restricted users only see orgs they are a member of
-				accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID})))
-			}
-			// Don't forget about self
-			accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID})
-			cond = cond.And(accessCond)
-		}
-
-	} else {
-		// Force visibility for privacy
-		// Not logged in - only public users
-		cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
-	}
+	cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
 
 	if opts.UID > 0 {
 		cond = cond.And(builder.Eq{"id": opts.UID})
@@ -130,7 +105,7 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
 	defer sessCount.Close()
 	count, err := sessCount.Count(new(User))
 	if err != nil {
-		return nil, 0, fmt.Errorf("Count: %v", err)
+		return nil, 0, fmt.Errorf("Count: %w", err)
 	}
 
 	if len(opts.OrderBy) == 0 {
@@ -149,24 +124,25 @@ func SearchUsers(opts *SearchUserOptions) (users []*User, _ int64, _ error) {
 	return users, count, sessQuery.Find(&users)
 }
 
-// IterateUser iterate users
-func IterateUser(f func(user *User) error) error {
-	var start int
-	batchSize := setting.Database.IterateBufferSize
-	for {
-		users := make([]*User, 0, batchSize)
-		if err := db.GetEngine(db.DefaultContext).Limit(batchSize, start).Find(&users); err != nil {
-			return err
-		}
-		if len(users) == 0 {
-			return nil
-		}
-		start += len(users)
-
-		for _, user := range users {
-			if err := f(user); err != nil {
-				return err
+// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
+func BuildCanSeeUserCondition(actor *User) builder.Cond {
+	if actor != nil {
+		// If Admin - they see all users!
+		if !actor.IsAdmin {
+			// Users can see an organization they are a member of
+			cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
+			if !actor.IsRestricted {
+				// Not-Restricted users can see public and limited users/organizations
+				cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
 			}
+			// Don't forget about self
+			return cond.Or(builder.Eq{"`user`.id": actor.ID})
 		}
+
+		return nil
 	}
+
+	// Force visibility for privacy
+	// Not logged in - only public users
+	return builder.In("`user`.visibility", structs.VisibleTypePublic)
 }
diff --git a/models/user/setting.go b/models/user/setting.go
index fbb6fbab30337..896f3c8da12d0 100644
--- a/models/user/setting.go
+++ b/models/user/setting.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/cache"
 
 	"xorm.io/builder"
 )
@@ -31,8 +32,52 @@ func init() {
 	db.RegisterModel(new(Setting))
 }
 
-// GetUserSettings returns specific settings from user
-func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
+// ErrUserSettingIsNotExist represents an error that a setting is not exist with special key
+type ErrUserSettingIsNotExist struct {
+	Key string
+}
+
+// Error implements error
+func (err ErrUserSettingIsNotExist) Error() string {
+	return fmt.Sprintf("Setting[%s] is not exist", err.Key)
+}
+
+// IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist
+func IsErrUserSettingIsNotExist(err error) bool {
+	_, ok := err.(ErrUserSettingIsNotExist)
+	return ok
+}
+
+// genSettingCacheKey returns the cache key for some configuration
+func genSettingCacheKey(userID int64, key string) string {
+	return fmt.Sprintf("user_%d.setting.%s", userID, key)
+}
+
+// GetSetting returns the setting value via the key
+func GetSetting(uid int64, key string) (*Setting, error) {
+	return cache.Get(genSettingCacheKey(uid, key), func() (*Setting, error) {
+		res, err := GetSettingNoCache(uid, key)
+		if err != nil {
+			return nil, err
+		}
+		return res, nil
+	})
+}
+
+// GetSettingNoCache returns specific setting without using the cache
+func GetSettingNoCache(uid int64, key string) (*Setting, error) {
+	v, err := GetSettings(uid, []string{key})
+	if err != nil {
+		return nil, err
+	}
+	if len(v) == 0 {
+		return nil, ErrUserSettingIsNotExist{key}
+	}
+	return v[key], nil
+}
+
+// GetSettings returns specific settings from user
+func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
 	settings := make([]*Setting, 0, len(keys))
 	if err := db.GetEngine(db.DefaultContext).
 		Where("user_id=?", uid).
@@ -77,6 +122,7 @@ func GetUserSetting(userID int64, key string, def ...string) (string, error) {
 	if err := validateUserSettingKey(key); err != nil {
 		return "", err
 	}
+
 	setting := &Setting{UserID: userID, SettingKey: key}
 	has, err := db.GetEngine(db.DefaultContext).Get(setting)
 	if err != nil {
@@ -96,7 +142,10 @@ func DeleteUserSetting(userID int64, key string) error {
 	if err := validateUserSettingKey(key); err != nil {
 		return err
 	}
+
+	cache.Remove(genSettingCacheKey(userID, key))
 	_, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key})
+
 	return err
 }
 
@@ -105,7 +154,12 @@ func SetUserSetting(userID int64, key, value string) error {
 	if err := validateUserSettingKey(key); err != nil {
 		return err
 	}
-	return upsertUserSettingValue(userID, key, value)
+
+	_, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
+		return value, upsertUserSettingValue(userID, key, value)
+	})
+
+	return err
 }
 
 func upsertUserSettingValue(userID int64, key, value string) error {
diff --git a/models/user/setting_keys.go b/models/user/setting_keys.go
index 109b5dd916365..d48ac930527a6 100644
--- a/models/user/setting_keys.go
+++ b/models/user/setting_keys.go
@@ -9,4 +9,8 @@ const (
 	SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
 	// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
 	SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
+	// UserActivityPubPrivPem is user's private key
+	UserActivityPubPrivPem = "activitypub.priv_pem"
+	// UserActivityPubPubPem is user's public key
+	UserActivityPubPubPem = "activitypub.pub_pem"
 )
diff --git a/models/user/setting_test.go b/models/user/setting_test.go
index f0083038df0f1..5a772a8ce7cf7 100644
--- a/models/user/setting_test.go
+++ b/models/user/setting_test.go
@@ -27,7 +27,7 @@ func TestSettings(t *testing.T) {
 	assert.NoError(t, err)
 
 	// get specific setting
-	settings, err := user_model.GetUserSettings(99, []string{keyName})
+	settings, err := user_model.GetSettings(99, []string{keyName})
 	assert.NoError(t, err)
 	assert.Len(t, settings, 1)
 	assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
diff --git a/models/user/user.go b/models/user/user.go
index f7d457b91b5a5..9a2da6dbc1a91 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -64,12 +64,14 @@ var AvailableHashAlgorithms = []string{
 }
 
 const (
-	// EmailNotificationsEnabled indicates that the user would like to receive all email notifications
+	// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
 	EmailNotificationsEnabled = "enabled"
 	// EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned.
 	EmailNotificationsOnMention = "onmention"
 	// EmailNotificationsDisabled indicates that the user would not like to be notified via email.
 	EmailNotificationsDisabled = "disabled"
+	// EmailNotificationsEnabled indicates that the user would like to receive all email notifications and your own
+	EmailNotificationsAndYourOwn = "andyourown"
 )
 
 // User represents the object of individual and member of organization.
@@ -86,7 +88,7 @@ type User struct {
 	PasswdHashAlgo               string `xorm:"NOT NULL DEFAULT 'argon2'"`
 
 	// MustChangePassword is an attribute that determines if a user
-	// is to change his/her password after registration.
+	// is to change their password after registration.
 	MustChangePassword bool `xorm:"NOT NULL DEFAULT false"`
 
 	LoginType   auth.Type
@@ -316,37 +318,45 @@ func (u *User) GenerateEmailActivateCode(email string) string {
 }
 
 // GetUserFollowers returns range of user's followers.
-func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) {
-	sess := db.GetEngine(db.DefaultContext).
+func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
+	sess := db.GetEngine(ctx).
+		Select("`user`.*").
+		Join("LEFT", "follow", "`user`.id=follow.user_id").
 		Where("follow.follow_id=?", u.ID).
-		Join("LEFT", "follow", "`user`.id=follow.user_id")
+		And(isUserVisibleToViewerCond(viewer))
 
 	if listOptions.Page != 0 {
 		sess = db.SetSessionPagination(sess, &listOptions)
 
 		users := make([]*User, 0, listOptions.PageSize)
-		return users, sess.Find(&users)
+		count, err := sess.FindAndCount(&users)
+		return users, count, err
 	}
 
 	users := make([]*User, 0, 8)
-	return users, sess.Find(&users)
+	count, err := sess.FindAndCount(&users)
+	return users, count, err
 }
 
 // GetUserFollowing returns range of user's following.
-func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) {
+func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
 	sess := db.GetEngine(db.DefaultContext).
+		Select("`user`.*").
+		Join("LEFT", "follow", "`user`.id=follow.follow_id").
 		Where("follow.user_id=?", u.ID).
-		Join("LEFT", "follow", "`user`.id=follow.follow_id")
+		And(isUserVisibleToViewerCond(viewer))
 
 	if listOptions.Page != 0 {
 		sess = db.SetSessionPagination(sess, &listOptions)
 
 		users := make([]*User, 0, listOptions.PageSize)
-		return users, sess.Find(&users)
+		count, err := sess.FindAndCount(&users)
+		return users, count, err
 	}
 
 	users := make([]*User, 0, 8)
-	return users, sess.Find(&users)
+	count, err := sess.FindAndCount(&users)
+	return users, count, err
 }
 
 // NewGitSig generates and returns the signature of given user.
@@ -485,6 +495,9 @@ func (u *User) GitName() string {
 
 // ShortName ellipses username to length
 func (u *User) ShortName(length int) string {
+	if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
+		return base.EllipsisString(u.FullName, length)
+	}
 	return base.EllipsisString(u.Name, length)
 }
 
@@ -537,7 +550,7 @@ func GetUserSalt() (string, error) {
 	return hex.EncodeToString(rBytes), nil
 }
 
-// NewGhostUser creates and returns a fake user for someone has deleted his/her account.
+// NewGhostUser creates and returns a fake user for someone has deleted their account.
 func NewGhostUser() *User {
 	return &User{
 		ID:        -1,
@@ -814,12 +827,12 @@ func ChangeUserName(u *User, newUserName string) (err error) {
 	}
 
 	if _, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
-		return fmt.Errorf("Change repo owner name: %v", err)
+		return fmt.Errorf("Change repo owner name: %w", err)
 	}
 
 	// Do not fail if directory does not exist
 	if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
-		return fmt.Errorf("Rename user directory: %v", err)
+		return fmt.Errorf("Rename user directory: %w", err)
 	}
 
 	if err = NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
@@ -880,14 +893,19 @@ func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...s
 		if err != nil {
 			return err
 		}
-		if !has {
-			// 1. Update old primary email
-			if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{
-				IsPrimary: false,
-			}); err != nil {
-				return err
+		if has && emailAddress.UID != u.ID {
+			return ErrEmailAlreadyUsed{
+				Email: u.Email,
 			}
+		}
+		// 1. Update old primary email
+		if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{
+			IsPrimary: false,
+		}); err != nil {
+			return err
+		}
 
+		if !has {
 			emailAddress.Email = u.Email
 			emailAddress.UID = u.ID
 			emailAddress.IsActivated = true
@@ -1034,7 +1052,7 @@ func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) {
 			Where("`type` = ?", UserTypeIndividual).
 			And("`prohibit_login` = ?", false).
 			And("`is_active` = ?", true).
-			And("`email_notifications_preference` IN ( ?, ?)", EmailNotificationsEnabled, EmailNotificationsOnMention).
+			In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsOnMention, EmailNotificationsAndYourOwn).
 			Find(&ous)
 	}
 
@@ -1042,7 +1060,7 @@ func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) {
 		Where("`type` = ?", UserTypeIndividual).
 		And("`prohibit_login` = ?", false).
 		And("`is_active` = ?", true).
-		And("`email_notifications_preference` = ?", EmailNotificationsEnabled).
+		In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsAndYourOwn).
 		Find(&ous)
 }
 
@@ -1219,9 +1237,42 @@ func GetAdminUser() (*User, error) {
 	return &admin, nil
 }
 
+func isUserVisibleToViewerCond(viewer *User) builder.Cond {
+	if viewer != nil && viewer.IsAdmin {
+		return builder.NewCond()
+	}
+
+	if viewer == nil || viewer.IsRestricted {
+		return builder.Eq{
+			"`user`.visibility": structs.VisibleTypePublic,
+		}
+	}
+
+	return builder.Neq{
+		"`user`.visibility": structs.VisibleTypePrivate,
+	}.Or(
+		builder.In("`user`.id",
+			builder.
+				Select("`follow`.user_id").
+				From("follow").
+				Where(builder.Eq{"`follow`.follow_id": viewer.ID})),
+		builder.In("`user`.id",
+			builder.
+				Select("`team_user`.uid").
+				From("team_user").
+				Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id").
+				Where(builder.Eq{"`t2`.uid": viewer.ID})),
+		builder.In("`user`.id",
+			builder.
+				Select("`team_user`.uid").
+				From("team_user").
+				Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id").
+				Where(builder.Eq{"`t2`.uid": viewer.ID})))
+}
+
 // IsUserVisibleToViewer check if viewer is able to see user profile
 func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
-	if viewer != nil && viewer.IsAdmin {
+	if viewer != nil && (viewer.IsAdmin || viewer.ID == u.ID) {
 		return true
 	}
 
@@ -1260,7 +1311,7 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
 			return false
 		}
 
-		if count < 0 {
+		if count == 0 {
 			// No common organization
 			return false
 		}
@@ -1270,3 +1321,20 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
 	}
 	return false
 }
+
+// CountWrongUserType count OrgUser who have wrong type
+func CountWrongUserType() (int64, error) {
+	return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User))
+}
+
+// FixWrongUserType fix OrgUser who have wrong type
+func FixWrongUserType() (int64, error) {
+	return db.GetEngine(db.DefaultContext).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1})
+}
+
+func GetOrderByName() string {
+	if setting.UI.DefaultShowFullName {
+		return "full_name, name"
+	}
+	return "name"
+}
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 4994ac53ab3df..5f2ac0a60c17d 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -22,7 +22,7 @@ import (
 
 func TestOAuth2Application_LoadUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	app := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: 1}).(*auth.OAuth2Application)
+	app := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: 1})
 	user, err := user_model.GetUserByID(app.UID)
 	assert.NoError(t, err)
 	assert.NotNil(t, user)
@@ -41,10 +41,10 @@ func TestGetUserEmailsByNames(t *testing.T) {
 func TestCanCreateOrganization(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 	assert.True(t, admin.CanCreateOrganization())
 
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	assert.True(t, user.CanCreateOrganization())
 	// Disable user create organization permission.
 	user.AllowCreateOrganization = false
@@ -102,7 +102,7 @@ func TestSearchUsers(t *testing.T) {
 		[]int64{9})
 
 	testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
-		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 32})
+		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32})
 
 	testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue},
 		[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
@@ -141,7 +141,7 @@ func TestEmailNotificationPreferences(t *testing.T) {
 		{user_model.EmailNotificationsEnabled, 8},
 		{user_model.EmailNotificationsOnMention, 9},
 	} {
-		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID}).(*user_model.User)
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.userID})
 		assert.Equal(t, test.expected, user.EmailNotifications())
 
 		// Try all possible settings
@@ -153,6 +153,9 @@ func TestEmailNotificationPreferences(t *testing.T) {
 
 		assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsDisabled))
 		assert.Equal(t, user_model.EmailNotificationsDisabled, user.EmailNotifications())
+
+		assert.NoError(t, user_model.SetEmailNotifications(user, user_model.EmailNotificationsAndYourOwn))
+		assert.Equal(t, user_model.EmailNotificationsAndYourOwn, user.EmailNotifications())
 	}
 }
 
@@ -239,7 +242,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
 func TestCreateUserEmailAlreadyUsed(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	// add new user with user2's email
 	user.Name = "testuser"
@@ -285,29 +288,45 @@ func TestGetMaileableUsersByIDs(t *testing.T) {
 
 func TestUpdateUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 
 	user.KeepActivityPrivate = true
 	assert.NoError(t, user_model.UpdateUser(db.DefaultContext, user, false))
-	user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	assert.True(t, user.KeepActivityPrivate)
 
 	setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false}
 	user.KeepActivityPrivate = false
 	user.Visibility = structs.VisibleTypePrivate
 	assert.Error(t, user_model.UpdateUser(db.DefaultContext, user, false))
-	user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	assert.True(t, user.KeepActivityPrivate)
 
+	newEmail := "new_" + user.Email
+	user.Email = newEmail
+	assert.NoError(t, user_model.UpdateUser(db.DefaultContext, user, true))
+	user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	assert.Equal(t, newEmail, user.Email)
+
 	user.Email = "no mail@mail.org"
 	assert.Error(t, user_model.UpdateUser(db.DefaultContext, user, true))
 }
 
+func TestUpdateUserEmailAlreadyUsed(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
+
+	user2.Email = user3.Email
+	err := user_model.UpdateUser(db.DefaultContext, user2, true)
+	assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
+}
+
 func TestNewUserRedirect(t *testing.T) {
 	// redirect to a completely new name
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
 
 	unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
@@ -324,7 +343,7 @@ func TestNewUserRedirect2(t *testing.T) {
 	// redirect to previously used name
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "olduser1"))
 
 	unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
@@ -341,7 +360,7 @@ func TestNewUserRedirect3(t *testing.T) {
 	// redirect for a previously-unredirected user
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 	assert.NoError(t, user_model.NewUserRedirect(db.DefaultContext, user.ID, user.Name, "newusername"))
 
 	unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{
@@ -397,3 +416,56 @@ func TestUnfollowUser(t *testing.T) {
 
 	unittest.CheckConsistencyFor(t, &user_model.User{})
 }
+
+func TestIsUserVisibleToViewer(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})   // admin, public
+	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})   // normal, public
+	user20 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) // public, same team as user31
+	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) // public, is restricted
+	user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31}) // private, same team as user20
+	user33 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 33}) // limited, follows 31
+
+	test := func(u, viewer *user_model.User, expected bool) {
+		name := func(u *user_model.User) string {
+			if u == nil {
+				return ""
+			}
+			return u.Name
+		}
+		assert.Equal(t, expected, user_model.IsUserVisibleToViewer(db.DefaultContext, u, viewer), "user %v should be visible to viewer %v: %v", name(u), name(viewer), expected)
+	}
+
+	// admin viewer
+	test(user1, user1, true)
+	test(user20, user1, true)
+	test(user31, user1, true)
+	test(user33, user1, true)
+
+	// non admin viewer
+	test(user4, user4, true)
+	test(user20, user4, true)
+	test(user31, user4, false)
+	test(user33, user4, true)
+	test(user4, nil, true)
+
+	// public user
+	test(user4, user20, true)
+	test(user4, user31, true)
+	test(user4, user33, true)
+
+	// limited user
+	test(user33, user33, true)
+	test(user33, user4, true)
+	test(user33, user29, false)
+	test(user33, nil, false)
+
+	// private user
+	test(user31, user31, true)
+	test(user31, user4, false)
+	test(user31, user20, true)
+	test(user31, user29, false)
+	test(user31, user33, true)
+	test(user31, nil, false)
+}
diff --git a/models/user/user_update.go b/models/user/user_update.go
new file mode 100644
index 0000000000000..9c9dc09bb234f
--- /dev/null
+++ b/models/user/user_update.go
@@ -0,0 +1,16 @@
+// 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 user
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/models/db"
+)
+
+func IncrUserRepoNum(ctx context.Context, userID int64) error {
+	_, err := db.GetEngine(ctx).Incr("num_repos").ID(userID).Update(new(User))
+	return err
+}
diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go
index c71b18f66253b..2b9b63c09bf59 100644
--- a/models/webhook/hooktask.go
+++ b/models/webhook/hooktask.go
@@ -47,6 +47,7 @@ const (
 	HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected"
 	HookEventPullRequestReviewComment  HookEventType = "pull_request_review_comment"
 	HookEventPullRequestSync           HookEventType = "pull_request_sync"
+	HookEventWiki                      HookEventType = "wiki"
 	HookEventRepository                HookEventType = "repository"
 	HookEventRelease                   HookEventType = "release"
 	HookEventPackage                   HookEventType = "package"
@@ -76,6 +77,8 @@ func (h HookEventType) Event() string {
 		return "pull_request_rejected"
 	case HookEventPullRequestReviewComment:
 		return "pull_request_comment"
+	case HookEventWiki:
+		return "wiki"
 	case HookEventRepository:
 		return "repository"
 	case HookEventRelease:
@@ -101,11 +104,10 @@ type HookResponse struct {
 // HookTask represents a hook task.
 type HookTask struct {
 	ID              int64 `xorm:"pk autoincr"`
-	RepoID          int64 `xorm:"INDEX"`
 	HookID          int64
 	UUID            string
 	api.Payloader   `xorm:"-"`
-	PayloadContent  string `xorm:"TEXT"`
+	PayloadContent  string `xorm:"LONGTEXT"`
 	EventType       HookEventType
 	IsDelivered     bool
 	Delivered       int64
@@ -113,9 +115,9 @@ type HookTask struct {
 
 	// History info.
 	IsSucceed       bool
-	RequestContent  string        `xorm:"TEXT"`
+	RequestContent  string        `xorm:"LONGTEXT"`
 	RequestInfo     *HookRequest  `xorm:"-"`
-	ResponseContent string        `xorm:"TEXT"`
+	ResponseContent string        `xorm:"LONGTEXT"`
 	ResponseInfo    *HookResponse `xorm:"-"`
 }
 
@@ -175,14 +177,29 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
 
 // CreateHookTask creates a new hook task,
 // it handles conversion from Payload to PayloadContent.
-func CreateHookTask(t *HookTask) error {
+func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) {
 	data, err := t.Payloader.JSONPayload()
 	if err != nil {
-		return err
+		return nil, err
 	}
 	t.UUID = gouuid.New().String()
 	t.PayloadContent = string(data)
-	return db.Insert(db.DefaultContext, t)
+	return t, db.Insert(ctx, t)
+}
+
+func GetHookTaskByID(ctx context.Context, id int64) (*HookTask, error) {
+	t := &HookTask{}
+
+	has, err := db.GetEngine(ctx).ID(id).Get(t)
+	if err != nil {
+		return nil, err
+	}
+	if !has {
+		return nil, ErrHookTaskNotExist{
+			TaskID: id,
+		}
+	}
+	return t, nil
 }
 
 // UpdateHookTask updates information of hook task.
@@ -192,53 +209,36 @@ func UpdateHookTask(t *HookTask) error {
 }
 
 // ReplayHookTask copies a hook task to get re-delivered
-func ReplayHookTask(hookID int64, uuid string) (*HookTask, error) {
-	var newTask *HookTask
-
-	err := db.WithTx(func(ctx context.Context) error {
-		task := &HookTask{
+func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask, error) {
+	task := &HookTask{
+		HookID: hookID,
+		UUID:   uuid,
+	}
+	has, err := db.GetByBean(ctx, task)
+	if err != nil {
+		return nil, err
+	} else if !has {
+		return nil, ErrHookTaskNotExist{
 			HookID: hookID,
 			UUID:   uuid,
 		}
-		has, err := db.GetByBean(ctx, task)
-		if err != nil {
-			return err
-		} else if !has {
-			return ErrHookTaskNotExist{
-				HookID: hookID,
-				UUID:   uuid,
-			}
-		}
-
-		newTask = &HookTask{
-			UUID:           gouuid.New().String(),
-			RepoID:         task.RepoID,
-			HookID:         task.HookID,
-			PayloadContent: task.PayloadContent,
-			EventType:      task.EventType,
-		}
-		return db.Insert(ctx, newTask)
-	})
+	}
 
-	return newTask, err
+	newTask := &HookTask{
+		UUID:           gouuid.New().String(),
+		HookID:         task.HookID,
+		PayloadContent: task.PayloadContent,
+		EventType:      task.EventType,
+	}
+	return newTask, db.Insert(ctx, newTask)
 }
 
 // FindUndeliveredHookTasks represents find the undelivered hook tasks
-func FindUndeliveredHookTasks() ([]*HookTask, error) {
+func FindUndeliveredHookTasks(ctx context.Context) ([]*HookTask, error) {
 	tasks := make([]*HookTask, 0, 10)
-	if err := db.GetEngine(db.DefaultContext).Where("is_delivered=?", false).Find(&tasks); err != nil {
-		return nil, err
-	}
-	return tasks, nil
-}
-
-// FindRepoUndeliveredHookTasks represents find the undelivered hook tasks of one repository
-func FindRepoUndeliveredHookTasks(repoID int64) ([]*HookTask, error) {
-	tasks := make([]*HookTask, 0, 5)
-	if err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
-		return nil, err
-	}
-	return tasks, nil
+	return tasks, db.GetEngine(ctx).
+		Where("is_delivered=?", false).
+		Find(&tasks)
 }
 
 // CleanupHookTaskTable deletes rows from hook_task as needed.
@@ -247,7 +247,7 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType,
 
 	if cleanupType == OlderThan {
 		deleteOlderThan := time.Now().Add(-olderThan).UnixNano()
-		deletes, err := db.GetEngine(db.DefaultContext).
+		deletes, err := db.GetEngine(ctx).
 			Where("is_delivered = ? and delivered < ?", true, deleteOlderThan).
 			Delete(new(HookTask))
 		if err != nil {
@@ -256,7 +256,8 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType,
 		log.Trace("Deleted %d rows from hook_task", deletes)
 	} else if cleanupType == PerWebhook {
 		hookIDs := make([]int64, 0, 10)
-		err := db.GetEngine(db.DefaultContext).Table("webhook").
+		err := db.GetEngine(ctx).
+			Table("webhook").
 			Where("id > 0").
 			Cols("id").
 			Find(&hookIDs)
@@ -286,7 +287,7 @@ func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int)
 		Cols("hook_task.delivered").
 		Join("INNER", "webhook", "hook_task.hook_id = webhook.id").
 		OrderBy("hook_task.delivered desc").
-		Limit(1, int(numberDeliveriesToKeep)).
+		Limit(1, numberDeliveriesToKeep).
 		Find(&deliveryDates)
 	if err != nil {
 		return err
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index 1b79a414ade52..aebe0d6e72fe0 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -19,13 +19,6 @@ import (
 	"xorm.io/builder"
 )
 
-//  __      __      ___.   .__                   __
-// /  \    /  \ ____\_ |__ |  |__   ____   ____ |  | __
-// \   \/\/   // __ \| __ \|  |  \ /  _ \ /  _ \|  |/ /
-//  \        /\  ___/| \_\ \   Y  (  <_> |  <_> )    <
-//   \__/\  /  \___  >___  /___|  /\____/ \____/|__|_ \
-//        \/       \/    \/     \/                   \/
-
 // ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
 type ErrWebhookNotExist struct {
 	ID int64
@@ -41,8 +34,13 @@ func (err ErrWebhookNotExist) Error() string {
 	return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
 }
 
+func (err ErrWebhookNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
 type ErrHookTaskNotExist struct {
+	TaskID int64
 	HookID int64
 	UUID   string
 }
@@ -54,7 +52,11 @@ func IsErrHookTaskNotExist(err error) bool {
 }
 
 func (err ErrHookTaskNotExist) Error() string {
-	return fmt.Sprintf("hook task does not exist [hook: %d, uuid: %s]", err.HookID, err.UUID)
+	return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
+}
+
+func (err ErrHookTaskNotExist) Unwrap() error {
+	return util.ErrNotExist
 }
 
 // HookContentType is the content type of a web hook
@@ -132,6 +134,7 @@ type HookEvents struct {
 	PullRequestComment   bool `json:"pull_request_comment"`
 	PullRequestReview    bool `json:"pull_request_review"`
 	PullRequestSync      bool `json:"pull_request_sync"`
+	Wiki                 bool `json:"wiki"`
 	Repository           bool `json:"repository"`
 	Release              bool `json:"release"`
 	Package              bool `json:"package"`
@@ -328,6 +331,12 @@ func (w *Webhook) HasPullRequestSyncEvent() bool {
 		(w.ChooseEvents && w.HookEvents.PullRequestSync)
 }
 
+// HasWikiEvent returns true if hook enabled wiki event.
+func (w *Webhook) HasWikiEvent() bool {
+	return w.SendEverything ||
+		(w.ChooseEvents && w.HookEvent.Wiki)
+}
+
 // HasReleaseEvent returns if hook enabled release event.
 func (w *Webhook) HasReleaseEvent() bool {
 	return w.SendEverything ||
@@ -373,6 +382,7 @@ func (w *Webhook) EventCheckers() []struct {
 		{w.HasPullRequestRejectedEvent, HookEventPullRequestReviewRejected},
 		{w.HasPullRequestCommentEvent, HookEventPullRequestReviewComment},
 		{w.HasPullRequestSyncEvent, HookEventPullRequestSync},
+		{w.HasWikiEvent, HookEventWiki},
 		{w.HasRepositoryEvent, HookEventRepository},
 		{w.HasReleaseEvent, HookEventRelease},
 		{w.HasPackageEvent, HookEventPackage},
@@ -399,6 +409,10 @@ func CreateWebhook(ctx context.Context, w *Webhook) error {
 
 // CreateWebhooks creates multiple web hooks
 func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
+	// xorm returns err "no element on slice when insert" for empty slices.
+	if len(ws) == 0 {
+		return nil
+	}
 	for i := 0; i < len(ws); i++ {
 		ws[i].Type = strings.TrimSpace(ws[i].Type)
 	}
@@ -594,14 +608,14 @@ func DeleteDefaultSystemWebhook(id int64) error {
 func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error {
 	ws, err := GetDefaultWebhooks(ctx)
 	if err != nil {
-		return fmt.Errorf("GetDefaultWebhooks: %v", err)
+		return fmt.Errorf("GetDefaultWebhooks: %w", err)
 	}
 
 	for _, w := range ws {
 		w.ID = 0
 		w.RepoID = repoID
 		if err := CreateWebhook(ctx, w); err != nil {
-			return fmt.Errorf("CreateWebhook: %v", err)
+			return fmt.Errorf("CreateWebhook: %w", err)
 		}
 	}
 	return nil
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index 4bc811586d9a7..8c4838ebdc058 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -31,14 +31,14 @@ func TestIsValidHookContentType(t *testing.T) {
 
 func TestWebhook_History(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook)
+	webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
 	tasks, err := webhook.History(0)
 	assert.NoError(t, err)
 	if assert.Len(t, tasks, 1) {
 		assert.Equal(t, int64(1), tasks[0].ID)
 	}
 
-	webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook)
+	webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
 	tasks, err = webhook.History(0)
 	assert.NoError(t, err)
 	assert.Len(t, tasks, 0)
@@ -46,7 +46,7 @@ func TestWebhook_History(t *testing.T) {
 
 func TestWebhook_UpdateEvent(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}).(*Webhook)
+	webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
 	hookEvent := &HookEvent{
 		PushOnly:       true,
 		SendEverything: false,
@@ -71,7 +71,7 @@ func TestWebhook_EventsArray(t *testing.T) {
 		"issues", "issue_assign", "issue_label", "issue_milestone", "issue_comment",
 		"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
 		"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
-		"pull_request_review_comment", "pull_request_sync", "repository", "release",
+		"pull_request_review_comment", "pull_request_sync", "wiki", "repository", "release",
 		"package",
 	},
 		(&Webhook{
@@ -162,7 +162,7 @@ func TestGetWebhooksByOrgID(t *testing.T) {
 
 func TestUpdateWebhook(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	hook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}).(*Webhook)
+	hook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
 	hook.IsActive = true
 	hook.ContentType = ContentTypeForm
 	unittest.AssertNotExistsBean(t, hook)
@@ -208,19 +208,19 @@ func TestHookTasks(t *testing.T) {
 func TestCreateHookTask(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:    3,
 		HookID:    3,
 		Payloader: &api.PushPayload{},
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 }
 
 func TestUpdateHookTask(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	hook := unittest.AssertExistsAndLoadBean(t, &HookTask{ID: 1}).(*HookTask)
+	hook := unittest.AssertExistsAndLoadBean(t, &HookTask{ID: 1})
 	hook.PayloadContent = "new payload content"
 	hook.DeliveredString = "new delivered string"
 	hook.IsDelivered = true
@@ -232,14 +232,14 @@ func TestUpdateHookTask(t *testing.T) {
 func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      3,
 		HookID:      3,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: true,
 		Delivered:   time.Now().UnixNano(),
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 0))
@@ -249,13 +249,13 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
 func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      2,
 		HookID:      4,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: false,
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 0))
@@ -265,14 +265,14 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
 func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      2,
 		HookID:      4,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: true,
 		Delivered:   time.Now().UnixNano(),
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), PerWebhook, 168*time.Hour, 1))
@@ -282,14 +282,14 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
 func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      3,
 		HookID:      3,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: true,
 		Delivered:   time.Now().AddDate(0, 0, -8).UnixNano(),
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0))
@@ -299,13 +299,13 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
 func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      2,
 		HookID:      4,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: false,
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0))
@@ -315,14 +315,14 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
 func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	hookTask := &HookTask{
-		RepoID:      2,
 		HookID:      4,
 		Payloader:   &api.PushPayload{},
 		IsDelivered: true,
 		Delivered:   time.Now().AddDate(0, 0, -6).UnixNano(),
 	}
 	unittest.AssertNotExistsBean(t, hookTask)
-	assert.NoError(t, CreateHookTask(hookTask))
+	_, err := CreateHookTask(db.DefaultContext, hookTask)
+	assert.NoError(t, err)
 	unittest.AssertExistsAndLoadBean(t, hookTask)
 
 	assert.NoError(t, CleanupHookTaskTable(context.Background(), OlderThan, 168*time.Hour, 0))
diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go
new file mode 100644
index 0000000000000..9bcef69de18c5
--- /dev/null
+++ b/modules/activitypub/client.go
@@ -0,0 +1,124 @@
+// 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 activitypub
+
+import (
+	"bytes"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/proxy"
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/go-fed/httpsig"
+)
+
+const (
+	// ActivityStreamsContentType const
+	ActivityStreamsContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
+	httpsigExpirationTime      = 60
+)
+
+// Gets the current time as an RFC 2616 formatted string
+// RFC 2616 requires RFC 1123 dates but with GMT instead of UTC
+func CurrentTime() string {
+	return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT")
+}
+
+func containsRequiredHTTPHeaders(method string, headers []string) error {
+	var hasRequestTarget, hasDate, hasDigest bool
+	for _, header := range headers {
+		hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget
+		hasDate = hasDate || header == "Date"
+		hasDigest = hasDigest || header == "Digest"
+	}
+	if !hasRequestTarget {
+		return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget)
+	} else if !hasDate {
+		return fmt.Errorf("missing http header for %s: Date", method)
+	} else if !hasDigest && method != http.MethodGet {
+		return fmt.Errorf("missing http header for %s: Digest", method)
+	}
+	return nil
+}
+
+// Client struct
+type Client struct {
+	client      *http.Client
+	algs        []httpsig.Algorithm
+	digestAlg   httpsig.DigestAlgorithm
+	getHeaders  []string
+	postHeaders []string
+	priv        *rsa.PrivateKey
+	pubID       string
+}
+
+// NewClient function
+func NewClient(user *user_model.User, pubID string) (c *Client, err error) {
+	if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil {
+		return
+	} else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil {
+		return
+	}
+
+	priv, err := GetPrivateKey(user)
+	if err != nil {
+		return
+	}
+	privPem, _ := pem.Decode([]byte(priv))
+	privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
+	if err != nil {
+		return
+	}
+
+	c = &Client{
+		client: &http.Client{
+			Transport: &http.Transport{
+				Proxy: proxy.Proxy(),
+			},
+		},
+		algs:        setting.HttpsigAlgs,
+		digestAlg:   httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm),
+		getHeaders:  setting.Federation.GetHeaders,
+		postHeaders: setting.Federation.PostHeaders,
+		priv:        privParsed,
+		pubID:       pubID,
+	}
+	return c, err
+}
+
+// NewRequest function
+func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) {
+	buf := bytes.NewBuffer(b)
+	req, err = http.NewRequest(http.MethodPost, to, buf)
+	if err != nil {
+		return
+	}
+	req.Header.Add("Content-Type", ActivityStreamsContentType)
+	req.Header.Add("Date", CurrentTime())
+	req.Header.Add("User-Agent", "Gitea/"+setting.AppVer)
+	signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime)
+	if err != nil {
+		return
+	}
+	err = signer.SignRequest(c.priv, c.pubID, req, b)
+	return req, err
+}
+
+// Post function
+func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) {
+	var req *http.Request
+	if req, err = c.NewRequest(b, to); err != nil {
+		return
+	}
+	resp, err = c.client.Do(req)
+	return resp, err
+}
diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go
new file mode 100644
index 0000000000000..62068d53b3df1
--- /dev/null
+++ b/modules/activitypub/client_test.go
@@ -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 activitypub
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"regexp"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
+
+	_ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestActivityPubSignedPost(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	pubID := "https://example.com/pubID"
+	c, err := NewClient(user, pubID)
+	assert.NoError(t, err)
+
+	expected := "BODY"
+	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Regexp(t, regexp.MustCompile("^"+setting.Federation.DigestAlgorithm), r.Header.Get("Digest"))
+		assert.Contains(t, r.Header.Get("Signature"), pubID)
+		assert.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType)
+		body, err := io.ReadAll(r.Body)
+		assert.NoError(t, err)
+		assert.Equal(t, expected, string(body))
+		fmt.Fprintf(w, expected)
+	}))
+	defer srv.Close()
+
+	r, err := c.Post([]byte(expected), srv.URL)
+	assert.NoError(t, err)
+	defer r.Body.Close()
+	body, err := io.ReadAll(r.Body)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, string(body))
+}
diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go
new file mode 100644
index 0000000000000..7fa2b09265cec
--- /dev/null
+++ b/modules/activitypub/main_test.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package activitypub
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", ".."),
+	})
+}
diff --git a/modules/activitypub/user_settings.go b/modules/activitypub/user_settings.go
new file mode 100644
index 0000000000000..d192b9cdb2771
--- /dev/null
+++ b/modules/activitypub/user_settings.go
@@ -0,0 +1,45 @@
+// 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 activitypub
+
+import (
+	user_model "code.gitea.io/gitea/models/user"
+)
+
+// GetKeyPair function returns a user's private and public keys
+func GetKeyPair(user *user_model.User) (pub, priv string, err error) {
+	var settings map[string]*user_model.Setting
+	settings, err = user_model.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
+	if err != nil {
+		return
+	} else if len(settings) == 0 {
+		if priv, pub, err = GenerateKeyPair(); err != nil {
+			return
+		}
+		if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPrivPem, priv); err != nil {
+			return
+		}
+		if err = user_model.SetUserSetting(user.ID, user_model.UserActivityPubPubPem, pub); err != nil {
+			return
+		}
+		return
+	} else {
+		priv = settings[user_model.UserActivityPubPrivPem].SettingValue
+		pub = settings[user_model.UserActivityPubPubPem].SettingValue
+		return
+	}
+}
+
+// GetPublicKey function returns a user's public key
+func GetPublicKey(user *user_model.User) (pub string, err error) {
+	pub, _, err = GetKeyPair(user)
+	return pub, err
+}
+
+// GetPrivateKey function returns a user's private key
+func GetPrivateKey(user *user_model.User) (priv string, err error) {
+	_, priv, err = GetKeyPair(user)
+	return priv, err
+}
diff --git a/modules/activitypub/user_settings_test.go b/modules/activitypub/user_settings_test.go
new file mode 100644
index 0000000000000..beefde232f56d
--- /dev/null
+++ b/modules/activitypub/user_settings_test.go
@@ -0,0 +1,29 @@
+// 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 activitypub
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+
+	_ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestUserSettings(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	pub, priv, err := GetKeyPair(user1)
+	assert.NoError(t, err)
+	pub1, err := GetPublicKey(user1)
+	assert.NoError(t, err)
+	assert.Equal(t, pub, pub1)
+	priv1, err := GetPrivateKey(user1)
+	assert.NoError(t, err)
+	assert.Equal(t, priv, priv1)
+}
diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go
index 6ca75ed90f0d7..2de77de009185 100644
--- a/modules/avatar/avatar.go
+++ b/modules/avatar/avatar.go
@@ -30,7 +30,7 @@ func RandomImageSize(size int, data []byte) (image.Image, error) {
 	// we use white as background, and use dark colors to draw blocks
 	imgMaker, err := identicon.New(size, color.White, identicon.DarkColors...)
 	if err != nil {
-		return nil, fmt.Errorf("identicon.New: %v", err)
+		return nil, fmt.Errorf("identicon.New: %w", err)
 	}
 	return imgMaker.Make(data), nil
 }
@@ -46,7 +46,7 @@ func RandomImage(data []byte) (image.Image, error) {
 func Prepare(data []byte) (*image.Image, error) {
 	imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
 	if err != nil {
-		return nil, fmt.Errorf("DecodeConfig: %v", err)
+		return nil, fmt.Errorf("DecodeConfig: %w", err)
 	}
 	if imgCfg.Width > setting.Avatar.MaxWidth {
 		return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.Avatar.MaxWidth)
@@ -57,7 +57,7 @@ func Prepare(data []byte) (*image.Image, error) {
 
 	img, _, err := image.Decode(bytes.NewReader(data))
 	if err != nil {
-		return nil, fmt.Errorf("Decode: %v", err)
+		return nil, fmt.Errorf("Decode: %w", err)
 	}
 
 	if imgCfg.Width != imgCfg.Height {
diff --git a/modules/avatar/identicon/block.go b/modules/avatar/identicon/block.go
index 249a3e0958cdb..270f05e1b0da4 100644
--- a/modules/avatar/identicon/block.go
+++ b/modules/avatar/identicon/block.go
@@ -36,20 +36,20 @@ func drawBlock(img *image.Paletted, x, y, size, angle int, points []int) {
 
 // blank
 //
-//  --------
-//  |      |
-//  |      |
-//  |      |
-//  --------
+//	--------
+//	|      |
+//	|      |
+//	|      |
+//	--------
 func b0(img *image.Paletted, x, y, size, angle int) {}
 
 // full-filled
 //
-//  --------
-//  |######|
-//  |######|
-//  |######|
-//  --------
+//	--------
+//	|######|
+//	|######|
+//	|######|
+//	--------
 func b1(img *image.Paletted, x, y, size, angle int) {
 	for i := x; i < x+size; i++ {
 		for j := y; j < y+size; j++ {
@@ -59,12 +59,13 @@ func b1(img *image.Paletted, x, y, size, angle int) {
 }
 
 // a small block
-//  ----------
-//  |        |
-//  |  ####  |
-//  |  ####  |
-//  |        |
-//  ----------
+//
+//	----------
+//	|        |
+//	|  ####  |
+//	|  ####  |
+//	|        |
+//	----------
 func b2(img *image.Paletted, x, y, size, angle int) {
 	l := size / 4
 	x += l
@@ -79,15 +80,15 @@ func b2(img *image.Paletted, x, y, size, angle int) {
 
 // diamond
 //
-//  ---------
-//  |   #   |
-//  |  ###  |
-//  | ##### |
-//  |#######|
-//  | ##### |
-//  |  ###  |
-//  |   #   |
-//  ---------
+//	---------
+//	|   #   |
+//	|  ###  |
+//	| ##### |
+//	|#######|
+//	| ##### |
+//	|  ###  |
+//	|   #   |
+//	---------
 func b3(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, 0, []int{
@@ -101,13 +102,13 @@ func b3(img *image.Paletted, x, y, size, angle int) {
 
 // b4
 //
-//  -------
-//  |#####|
-//  |#### |
-//  |###  |
-//  |##   |
-//  |#    |
-//  |------
+//	-------
+//	|#####|
+//	|#### |
+//	|###  |
+//	|##   |
+//	|#    |
+//	|------
 func b4(img *image.Paletted, x, y, size, angle int) {
 	drawBlock(img, x, y, size, angle, []int{
 		0, 0,
@@ -119,11 +120,11 @@ func b4(img *image.Paletted, x, y, size, angle int) {
 
 // b5
 //
-//  ---------
-//  |   #   |
-//  |  ###  |
-//  | ##### |
-//  |#######|
+//	---------
+//	|   #   |
+//	|  ###  |
+//	| ##### |
+//	|#######|
 func b5(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -136,11 +137,11 @@ func b5(img *image.Paletted, x, y, size, angle int) {
 
 // b6
 //
-//  --------
-//  |###   |
-//  |###   |
-//  |###   |
-//  --------
+//	--------
+//	|###   |
+//	|###   |
+//	|###   |
+//	--------
 func b6(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -154,12 +155,12 @@ func b6(img *image.Paletted, x, y, size, angle int) {
 
 // b7 italic cone
 //
-//  ---------
-//  | #     |
-//  |  ##   |
-//  |  #####|
-//  |   ####|
-//  |--------
+//	---------
+//	| #     |
+//	|  ##   |
+//	|  #####|
+//	|   ####|
+//	|--------
 func b7(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -173,14 +174,14 @@ func b7(img *image.Paletted, x, y, size, angle int) {
 
 // b8 three small triangles
 //
-//  -----------
-//  |    #    |
-//  |   ###   |
-//  |  #####  |
-//  |  #   #  |
-//  | ### ### |
-//  |#########|
-//  -----------
+//	-----------
+//	|    #    |
+//	|   ###   |
+//	|  #####  |
+//	|  #   #  |
+//	| ### ### |
+//	|#########|
+//	-----------
 func b8(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	mm := m / 2
@@ -212,13 +213,13 @@ func b8(img *image.Paletted, x, y, size, angle int) {
 
 // b9 italic triangle
 //
-//  ---------
-//  |#      |
-//  | ####  |
-//  |  #####|
-//  |  #### |
-//  |   #   |
-//  ---------
+//	---------
+//	|#      |
+//	| ####  |
+//	|  #####|
+//	|  #### |
+//	|   #   |
+//	---------
 func b9(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -231,16 +232,16 @@ func b9(img *image.Paletted, x, y, size, angle int) {
 
 // b10
 //
-//  ----------
-//  |    ####|
-//  |    ### |
-//  |    ##  |
-//  |    #   |
-//  |####    |
-//  |###     |
-//  |##      |
-//  |#       |
-//  ----------
+//	----------
+//	|    ####|
+//	|    ### |
+//	|    ##  |
+//	|    #   |
+//	|####    |
+//	|###     |
+//	|##      |
+//	|#       |
+//	----------
 func b10(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -260,13 +261,13 @@ func b10(img *image.Paletted, x, y, size, angle int) {
 
 // b11
 //
-//  ----------
-//  |####    |
-//  |####    |
-//  |####    |
-//  |        |
-//  |        |
-//  ----------
+//	----------
+//	|####    |
+//	|####    |
+//	|####    |
+//	|        |
+//	|        |
+//	----------
 func b11(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -280,13 +281,13 @@ func b11(img *image.Paletted, x, y, size, angle int) {
 
 // b12
 //
-//  -----------
-//  |         |
-//  |         |
-//  |#########|
-//  |  #####  |
-//  |    #    |
-//  -----------
+//	-----------
+//	|         |
+//	|         |
+//	|#########|
+//	|  #####  |
+//	|    #    |
+//	-----------
 func b12(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -299,13 +300,13 @@ func b12(img *image.Paletted, x, y, size, angle int) {
 
 // b13
 //
-//  -----------
-//  |         |
-//  |         |
-//  |    #    |
-//  |  #####  |
-//  |#########|
-//  -----------
+//	-----------
+//	|         |
+//	|         |
+//	|    #    |
+//	|  #####  |
+//	|#########|
+//	-----------
 func b13(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -318,13 +319,13 @@ func b13(img *image.Paletted, x, y, size, angle int) {
 
 // b14
 //
-//  ---------
-//  |   #   |
-//  | ###   |
-//  |####   |
-//  |       |
-//  |       |
-//  ---------
+//	---------
+//	|   #   |
+//	| ###   |
+//	|####   |
+//	|       |
+//	|       |
+//	---------
 func b14(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -337,13 +338,13 @@ func b14(img *image.Paletted, x, y, size, angle int) {
 
 // b15
 //
-//  ----------
-//  |#####   |
-//  |###     |
-//  |#       |
-//  |        |
-//  |        |
-//  ----------
+//	----------
+//	|#####   |
+//	|###     |
+//	|#       |
+//	|        |
+//	|        |
+//	----------
 func b15(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -356,14 +357,14 @@ func b15(img *image.Paletted, x, y, size, angle int) {
 
 // b16
 //
-//  ---------
-//  |   #   |
-//  | ##### |
-//  |#######|
-//  |   #   |
-//  | ##### |
-//  |#######|
-//  ---------
+//	---------
+//	|   #   |
+//	| ##### |
+//	|#######|
+//	|   #   |
+//	| ##### |
+//	|#######|
+//	---------
 func b16(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	drawBlock(img, x, y, size, angle, []int{
@@ -383,13 +384,13 @@ func b16(img *image.Paletted, x, y, size, angle int) {
 
 // b17
 //
-//  ----------
-//  |#####   |
-//  |###     |
-//  |#       |
-//  |      ##|
-//  |      ##|
-//  ----------
+//	----------
+//	|#####   |
+//	|###     |
+//	|#       |
+//	|      ##|
+//	|      ##|
+//	----------
 func b17(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 
@@ -412,13 +413,13 @@ func b17(img *image.Paletted, x, y, size, angle int) {
 
 // b18
 //
-//  ----------
-//  |#####   |
-//  |####    |
-//  |###     |
-//  |##      |
-//  |#       |
-//  ----------
+//	----------
+//	|#####   |
+//	|####    |
+//	|###     |
+//	|##      |
+//	|#       |
+//	----------
 func b18(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 
@@ -432,13 +433,13 @@ func b18(img *image.Paletted, x, y, size, angle int) {
 
 // b19
 //
-//  ----------
-//  |########|
-//  |###  ###|
-//  |#      #|
-//  |###  ###|
-//  |########|
-//  ----------
+//	----------
+//	|########|
+//	|###  ###|
+//	|#      #|
+//	|###  ###|
+//	|########|
+//	----------
 func b19(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 
@@ -473,13 +474,13 @@ func b19(img *image.Paletted, x, y, size, angle int) {
 
 // b20
 //
-//  ----------
-//  |  ##     |
-//  |###      |
-//  |##       |
-//  |##       |
-//  |#        |
-//  ----------
+//	----------
+//	|  ##     |
+//	|###      |
+//	|##       |
+//	|##       |
+//	|#        |
+//	----------
 func b20(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -494,13 +495,13 @@ func b20(img *image.Paletted, x, y, size, angle int) {
 
 // b21
 //
-//  ----------
-//  | ####   |
-//  |## #####|
-//  |##    ##|
-//  |##      |
-//  |#       |
-//  ----------
+//	----------
+//	| ####   |
+//	|## #####|
+//	|##    ##|
+//	|##      |
+//	|#       |
+//	----------
 func b21(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -522,13 +523,13 @@ func b21(img *image.Paletted, x, y, size, angle int) {
 
 // b22
 //
-//  ----------
-//  | ####   |
-//  |##  ### |
-//  |##    ##|
-//  |##    ##|
-//  |#      #|
-//  ----------
+//	----------
+//	| ####   |
+//	|##  ### |
+//	|##    ##|
+//	|##    ##|
+//	|#      #|
+//	----------
 func b22(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -550,13 +551,13 @@ func b22(img *image.Paletted, x, y, size, angle int) {
 
 // b23
 //
-//  ----------
-//  | #######|
-//  |###    #|
-//  |##      |
-//  |##      |
-//  |#       |
-//  ----------
+//	----------
+//	| #######|
+//	|###    #|
+//	|##      |
+//	|##      |
+//	|#       |
+//	----------
 func b23(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -578,13 +579,13 @@ func b23(img *image.Paletted, x, y, size, angle int) {
 
 // b24
 //
-//  ----------
-//  | ##  ###|
-//  |###  ###|
-//  |##  ##  |
-//  |##  ##  |
-//  |#   #   |
-//  ----------
+//	----------
+//	| ##  ###|
+//	|###  ###|
+//	|##  ##  |
+//	|##  ##  |
+//	|#   #   |
+//	----------
 func b24(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -606,13 +607,13 @@ func b24(img *image.Paletted, x, y, size, angle int) {
 
 // b25
 //
-//  ----------
-//  |#      #|
-//  |##   ###|
-//  |##  ##  |
-//  |######  |
-//  |####    |
-//  ----------
+//	----------
+//	|#      #|
+//	|##   ###|
+//	|##  ##  |
+//	|######  |
+//	|####    |
+//	----------
 func b25(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -634,13 +635,13 @@ func b25(img *image.Paletted, x, y, size, angle int) {
 
 // b26
 //
-//  ----------
-//  |#      #|
-//  |###  ###|
-//  |  ####  |
-//  |###  ###|
-//  |#      #|
-//  ----------
+//	----------
+//	|#      #|
+//	|###  ###|
+//	|  ####  |
+//	|###  ###|
+//	|#      #|
+//	----------
 func b26(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
@@ -676,13 +677,13 @@ func b26(img *image.Paletted, x, y, size, angle int) {
 
 // b27
 //
-//  ----------
-//  |########|
-//  |##   ###|
-//  |#      #|
-//  |###   ##|
-//  |########|
-//  ----------
+//	----------
+//	|########|
+//	|##   ###|
+//	|#      #|
+//	|###   ##|
+//	|########|
+//	----------
 func b27(img *image.Paletted, x, y, size, angle int) {
 	m := size / 2
 	q := size / 4
diff --git a/modules/base/natural_sort.go b/modules/base/natural_sort.go
index 60db363df0856..46cdd52932569 100644
--- a/modules/base/natural_sort.go
+++ b/modules/base/natural_sort.go
@@ -55,7 +55,7 @@ func isDecimal(r rune) bool {
 }
 
 func compareByNumbers(str1 string, pos1 int, str2 string, pos2 int) (i1, i2 int, less, equal bool) {
-	var d1, d2 bool = true, true
+	d1, d2 := true, true
 	var dec1, dec2 string
 	for d1 || d2 {
 		if d1 {
diff --git a/modules/base/tool.go b/modules/base/tool.go
index a981fd6c57dc9..f1e4a3bf9783d 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -241,15 +241,6 @@ func Int64sToStrings(ints []int64) []string {
 	return strs
 }
 
-// Int64sToMap converts a slice of int64 to a int64 map.
-func Int64sToMap(ints []int64) map[int64]bool {
-	m := make(map[int64]bool)
-	for _, i := range ints {
-		m[i] = true
-	}
-	return m
-}
-
 // Int64sContains returns if a int64 in a slice of int64
 func Int64sContains(intsSlice []int64, a int64) bool {
 	for _, c := range intsSlice {
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index 6685168bacd88..87de898e0b801 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -214,16 +214,7 @@ func TestInt64sToStrings(t *testing.T) {
 	)
 }
 
-func TestInt64sToMap(t *testing.T) {
-	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
-	assert.Equal(t,
-		map[int64]bool{1: true, 4: true, 16: true},
-		Int64sToMap([]int64{1, 4, 16}),
-	)
-}
-
 func TestInt64sContains(t *testing.T) {
-	assert.Equal(t, map[int64]bool{}, Int64sToMap([]int64{}))
 	assert.True(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 1))
 	assert.True(t, Int64sContains([]int64{2323}, 2323))
 	assert.False(t, Int64sContains([]int64{6, 44324, 4324, 32, 1, 2323}, 232))
diff --git a/modules/cache/cache.go b/modules/cache/cache.go
index 21d0cd0a04996..d98b0a0cec3b6 100644
--- a/modules/cache/cache.go
+++ b/modules/cache/cache.go
@@ -46,32 +46,64 @@ func GetCache() mc.Cache {
 	return conn
 }
 
+// Get returns the key value from cache with callback when no key exists in cache
+func Get[V interface{}](key string, getFunc func() (V, error)) (V, error) {
+	if conn == nil || setting.CacheService.TTL == 0 {
+		return getFunc()
+	}
+
+	cached := conn.Get(key)
+	if value, ok := cached.(V); ok {
+		return value, nil
+	}
+
+	value, err := getFunc()
+	if err != nil {
+		return value, err
+	}
+
+	return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
+}
+
+// Set updates and returns the key value in the cache with callback. The old value is only removed if the updateFunc() is successful
+func Set[V interface{}](key string, valueFunc func() (V, error)) (V, error) {
+	if conn == nil || setting.CacheService.TTL == 0 {
+		return valueFunc()
+	}
+
+	value, err := valueFunc()
+	if err != nil {
+		return value, err
+	}
+
+	return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
+}
+
 // GetString returns the key value from cache with callback when no key exists in cache
 func GetString(key string, getFunc func() (string, error)) (string, error) {
 	if conn == nil || setting.CacheService.TTL == 0 {
 		return getFunc()
 	}
-	if !conn.IsExist(key) {
-		var (
-			value string
-			err   error
-		)
-		if value, err = getFunc(); err != nil {
-			return value, err
-		}
-		err = conn.Put(key, value, setting.CacheService.TTLSeconds())
+
+	cached := conn.Get(key)
+
+	if cached == nil {
+		value, err := getFunc()
 		if err != nil {
-			return "", err
+			return value, err
 		}
+		return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
 	}
-	value := conn.Get(key)
-	if v, ok := value.(string); ok {
-		return v, nil
+
+	if value, ok := cached.(string); ok {
+		return value, nil
 	}
-	if v, ok := value.(fmt.Stringer); ok {
-		return v.String(), nil
+
+	if stringer, ok := cached.(fmt.Stringer); ok {
+		return stringer.String(), nil
 	}
-	return fmt.Sprintf("%s", conn.Get(key)), nil
+
+	return fmt.Sprintf("%s", cached), nil
 }
 
 // GetInt returns key value from cache with callback when no key exists in cache
@@ -79,30 +111,33 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
 	if conn == nil || setting.CacheService.TTL == 0 {
 		return getFunc()
 	}
-	if !conn.IsExist(key) {
-		var (
-			value int
-			err   error
-		)
-		if value, err = getFunc(); err != nil {
-			return value, err
-		}
-		err = conn.Put(key, value, setting.CacheService.TTLSeconds())
+
+	cached := conn.Get(key)
+
+	if cached == nil {
+		value, err := getFunc()
 		if err != nil {
-			return 0, err
+			return value, err
 		}
+
+		return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
 	}
-	switch value := conn.Get(key).(type) {
+
+	switch v := cached.(type) {
 	case int:
-		return value, nil
+		return v, nil
 	case string:
-		v, err := strconv.Atoi(value)
+		value, err := strconv.Atoi(v)
 		if err != nil {
 			return 0, err
 		}
-		return v, nil
+		return value, nil
 	default:
-		return 0, fmt.Errorf("Unsupported cached value type: %v", value)
+		value, err := getFunc()
+		if err != nil {
+			return value, err
+		}
+		return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
 	}
 }
 
@@ -111,30 +146,34 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
 	if conn == nil || setting.CacheService.TTL == 0 {
 		return getFunc()
 	}
-	if !conn.IsExist(key) {
-		var (
-			value int64
-			err   error
-		)
-		if value, err = getFunc(); err != nil {
-			return value, err
-		}
-		err = conn.Put(key, value, setting.CacheService.TTLSeconds())
+
+	cached := conn.Get(key)
+
+	if cached == nil {
+		value, err := getFunc()
 		if err != nil {
-			return 0, err
+			return value, err
 		}
+
+		return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
 	}
-	switch value := conn.Get(key).(type) {
+
+	switch v := conn.Get(key).(type) {
 	case int64:
-		return value, nil
+		return v, nil
 	case string:
-		v, err := strconv.ParseInt(value, 10, 64)
+		value, err := strconv.ParseInt(v, 10, 64)
 		if err != nil {
 			return 0, err
 		}
-		return v, nil
+		return value, nil
 	default:
-		return 0, fmt.Errorf("Unsupported cached value type: %v", value)
+		value, err := getFunc()
+		if err != nil {
+			return value, err
+		}
+
+		return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
 	}
 }
 
diff --git a/modules/charset/ambiguous.go b/modules/charset/ambiguous.go
new file mode 100644
index 0000000000000..ef5e7650a6a7b
--- /dev/null
+++ b/modules/charset/ambiguous.go
@@ -0,0 +1,60 @@
+// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT
+// 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 charset
+
+import (
+	"sort"
+	"strings"
+	"unicode"
+
+	"code.gitea.io/gitea/modules/translation"
+)
+
+// AmbiguousTablesForLocale provides the table of ambiguous characters for this locale.
+func AmbiguousTablesForLocale(locale translation.Locale) []*AmbiguousTable {
+	key := locale.Language()
+	var table *AmbiguousTable
+	var ok bool
+	for len(key) > 0 {
+		if table, ok = AmbiguousCharacters[key]; ok {
+			break
+		}
+		idx := strings.LastIndexAny(key, "-_")
+		if idx < 0 {
+			key = ""
+		} else {
+			key = key[:idx]
+		}
+	}
+	if table == nil && (locale.Language() == "zh-CN" || locale.Language() == "zh_CN") {
+		table = AmbiguousCharacters["zh-hans"]
+	}
+	if table == nil && strings.HasPrefix(locale.Language(), "zh") {
+		table = AmbiguousCharacters["zh-hant"]
+	}
+	if table == nil {
+		table = AmbiguousCharacters["_default"]
+	}
+
+	return []*AmbiguousTable{
+		table,
+		AmbiguousCharacters["_common"],
+	}
+}
+
+func isAmbiguous(r rune, confusableTo *rune, tables ...*AmbiguousTable) bool {
+	for _, table := range tables {
+		if !unicode.Is(table.RangeTable, r) {
+			continue
+		}
+		i := sort.Search(len(table.Confusable), func(i int) bool {
+			return table.Confusable[i] >= r
+		})
+		(*confusableTo) = table.With[i]
+		return true
+	}
+	return false
+}
diff --git a/modules/charset/ambiguous/ambiguous.json b/modules/charset/ambiguous/ambiguous.json
new file mode 100644
index 0000000000000..d0f69f6ae2b17
--- /dev/null
+++ b/modules/charset/ambiguous/ambiguous.json
@@ -0,0 +1 @@
+"{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125],\"_default\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"cs\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"es\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"fr\":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ja\":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],\"ko\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pt-BR\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ru\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"zh-hans\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],\"zh-hant\":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}"
\ No newline at end of file
diff --git a/modules/charset/ambiguous/generate.go b/modules/charset/ambiguous/generate.go
new file mode 100644
index 0000000000000..7dd2821aae257
--- /dev/null
+++ b/modules/charset/ambiguous/generate.go
@@ -0,0 +1,189 @@
+// 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 main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/format"
+	"os"
+	"sort"
+	"text/template"
+	"unicode"
+
+	"code.gitea.io/gitea/modules/json"
+
+	"golang.org/x/text/unicode/rangetable"
+)
+
+// ambiguous.json provides a one to one mapping of ambiguous characters to other characters
+// See https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json
+
+type AmbiguousTable struct {
+	Confusable []rune
+	With       []rune
+	Locale     string
+	RangeTable *unicode.RangeTable
+}
+
+type RunePair struct {
+	Confusable rune
+	With       rune
+}
+
+var verbose bool
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `%s: Generate AmbiguousCharacter
+
+Usage: %[1]s [-v] [-o output.go] ambiguous.json
+`, os.Args[0])
+		flag.PrintDefaults()
+	}
+
+	output := ""
+	flag.BoolVar(&verbose, "v", false, "verbose output")
+	flag.StringVar(&output, "o", "ambiguous_gen.go", "file to output to")
+	flag.Parse()
+	input := flag.Arg(0)
+	if input == "" {
+		input = "ambiguous.json"
+	}
+
+	bs, err := os.ReadFile(input)
+	if err != nil {
+		fatalf("Unable to read: %s Err: %v", input, err)
+	}
+
+	var unwrapped string
+	if err := json.Unmarshal(bs, &unwrapped); err != nil {
+		fatalf("Unable to unwrap content in: %s Err: %v", input, err)
+	}
+
+	fromJSON := map[string][]uint32{}
+	if err := json.Unmarshal([]byte(unwrapped), &fromJSON); err != nil {
+		fatalf("Unable to unmarshal content in: %s Err: %v", input, err)
+	}
+
+	tables := make([]*AmbiguousTable, 0, len(fromJSON))
+	for locale, chars := range fromJSON {
+		table := &AmbiguousTable{Locale: locale}
+		table.Confusable = make([]rune, 0, len(chars)/2)
+		table.With = make([]rune, 0, len(chars)/2)
+		pairs := make([]RunePair, len(chars)/2)
+		for i := 0; i < len(chars); i += 2 {
+			pairs[i/2].Confusable, pairs[i/2].With = rune(chars[i]), rune(chars[i+1])
+		}
+		sort.Slice(pairs, func(i, j int) bool {
+			return pairs[i].Confusable < pairs[j].Confusable
+		})
+		for _, pair := range pairs {
+			table.Confusable = append(table.Confusable, pair.Confusable)
+			table.With = append(table.With, pair.With)
+		}
+		table.RangeTable = rangetable.New(table.Confusable...)
+		tables = append(tables, table)
+	}
+	sort.Slice(tables, func(i, j int) bool {
+		return tables[i].Locale < tables[j].Locale
+	})
+	data := map[string]interface{}{
+		"Tables": tables,
+	}
+
+	if err := runTemplate(generatorTemplate, output, &data); err != nil {
+		fatalf("Unable to run template: %v", err)
+	}
+}
+
+func runTemplate(t *template.Template, filename string, data interface{}) error {
+	buf := bytes.NewBuffer(nil)
+	if err := t.Execute(buf, data); err != nil {
+		return fmt.Errorf("unable to execute template: %w", err)
+	}
+	bs, err := format.Source(buf.Bytes())
+	if err != nil {
+		verbosef("Bad source:\n%s", buf.String())
+		return fmt.Errorf("unable to format source: %w", err)
+	}
+
+	old, err := os.ReadFile(filename)
+	if err != nil && !os.IsNotExist(err) {
+		return fmt.Errorf("failed to read old file %s because %w", filename, err)
+	} else if err == nil {
+		if bytes.Equal(bs, old) {
+			// files are the same don't rewrite it.
+			return nil
+		}
+	}
+
+	file, err := os.Create(filename)
+	if err != nil {
+		return fmt.Errorf("failed to create file %s because %w", filename, err)
+	}
+	defer file.Close()
+	_, err = file.Write(bs)
+	if err != nil {
+		return fmt.Errorf("unable to write generated source: %w", err)
+	}
+	return nil
+}
+
+var generatorTemplate = template.Must(template.New("ambiguousTemplate").Parse(`// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT
+// 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 charset
+
+import "unicode"
+
+// This file is generated from https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json
+
+// AmbiguousTable matches a confusable rune with its partner for the Locale
+type AmbiguousTable struct {
+	Confusable []rune
+	With       []rune
+	Locale     string
+	RangeTable *unicode.RangeTable
+}
+
+// AmbiguousCharacters provides a map by locale name to the confusable characters in that locale
+var AmbiguousCharacters = map[string]*AmbiguousTable{
+	{{range .Tables}}{{printf "%q:" .Locale}} {
+			Confusable: []rune{ {{range .Confusable}}{{.}},{{end}} },
+			With: []rune{ {{range .With}}{{.}},{{end}} },
+			Locale: {{printf "%q" .Locale}},
+			RangeTable: &unicode.RangeTable{
+				R16: []unicode.Range16{
+			{{range .RangeTable.R16 }}		{Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}},
+			{{end}}	},
+				R32: []unicode.Range32{
+			{{range .RangeTable.R32}}		{Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}},
+			{{end}}	},
+				LatinOffset: {{.RangeTable.LatinOffset}},
+			},
+		},
+	{{end}}
+}
+
+`))
+
+func logf(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format+"\n", args...)
+}
+
+func verbosef(format string, args ...interface{}) {
+	if verbose {
+		logf(format, args...)
+	}
+}
+
+func fatalf(format string, args ...interface{}) {
+	logf("fatal: "+format+"\n", args...)
+	os.Exit(1)
+}
diff --git a/modules/charset/ambiguous_gen.go b/modules/charset/ambiguous_gen.go
new file mode 100644
index 0000000000000..cc270affac52b
--- /dev/null
+++ b/modules/charset/ambiguous_gen.go
@@ -0,0 +1,837 @@
+// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT
+// 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 charset
+
+import "unicode"
+
+// This file is generated from https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json
+
+// AmbiguousTable matches a confusable rune with its partner for the Locale
+type AmbiguousTable struct {
+	Confusable []rune
+	With       []rune
+	Locale     string
+	RangeTable *unicode.RangeTable
+}
+
+// AmbiguousCharacters provides a map by locale name to the confusable characters in that locale
+var AmbiguousCharacters = map[string]*AmbiguousTable{
+	"_common": {
+		Confusable: []rune{184, 383, 388, 397, 422, 423, 439, 444, 445, 448, 451, 540, 546, 547, 577, 593, 609, 611, 617, 618, 623, 651, 655, 660, 697, 699, 700, 701, 702, 706, 707, 708, 710, 712, 714, 715, 720, 727, 731, 732, 756, 760, 884, 890, 894, 895, 900, 913, 914, 917, 918, 919, 922, 924, 925, 927, 929, 932, 933, 935, 945, 947, 953, 957, 959, 961, 963, 965, 978, 988, 1000, 1010, 1011, 1017, 1018, 1029, 1030, 1032, 1109, 1110, 1112, 1121, 1140, 1141, 1198, 1199, 1211, 1213, 1216, 1231, 1248, 1281, 1292, 1307, 1308, 1309, 1357, 1359, 1365, 1370, 1373, 1377, 1379, 1382, 1392, 1400, 1404, 1405, 1409, 1412, 1413, 1417, 1472, 1475, 1493, 1496, 1497, 1503, 1505, 1523, 1549, 1575, 1607, 1632, 1633, 1637, 1639, 1643, 1645, 1726, 1729, 1748, 1749, 1776, 1777, 1781, 1783, 1793, 1794, 1795, 1796, 1984, 1994, 2036, 2037, 2042, 2307, 2406, 2429, 2534, 2538, 2541, 2662, 2663, 2666, 2691, 2790, 2819, 2848, 2918, 2920, 3046, 3074, 3174, 3202, 3302, 3330, 3360, 3430, 3437, 3458, 3664, 3792, 4125, 4160, 4327, 4351, 4608, 4816, 5024, 5025, 5026, 5029, 5033, 5034, 5035, 5036, 5038, 5043, 5047, 5051, 5053, 5056, 5058, 5059, 5070, 5071, 5074, 5076, 5077, 5081, 5082, 5086, 5087, 5090, 5094, 5095, 5102, 5107, 5108, 5120, 5167, 5171, 5176, 5194, 5196, 5229, 5231, 5234, 5261, 5290, 5311, 5441, 5500, 5501, 5511, 5551, 5556, 5573, 5598, 5610, 5616, 5623, 5741, 5742, 5760, 5810, 5815, 5825, 5836, 5845, 5846, 5868, 5869, 5941, 6147, 6153, 7428, 7439, 7441, 7452, 7456, 7457, 7458, 7462, 7555, 7564, 7837, 7935, 8125, 8126, 8127, 8128, 8175, 8189, 8190, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8208, 8209, 8210, 8218, 8219, 8228, 8232, 8233, 8239, 8242, 8249, 8250, 8257, 8259, 8260, 8270, 8275, 8282, 8287, 8450, 8458, 8459, 8460, 8461, 8462, 8464, 8465, 8466, 8467, 8469, 8473, 8474, 8475, 8476, 8477, 8484, 8488, 8490, 8492, 8493, 8494, 8495, 8496, 8497, 8499, 8500, 8505, 8509, 8517, 8518, 8519, 8520, 8521, 8544, 8548, 8553, 8556, 8557, 8558, 8559, 8560, 8564, 8569, 8572, 8573, 8574, 8722, 8725, 8726, 8727, 8739, 8744, 8746, 8758, 8764, 8868, 8897, 8899, 8959, 9075, 9076, 9082, 9213, 9585, 9587, 10088, 10089, 10094, 10095, 10098, 10099, 10100, 10101, 10133, 10134, 10187, 10189, 10201, 10539, 10540, 10741, 10744, 10745, 10799, 11397, 11406, 11410, 11412, 11416, 11418, 11422, 11423, 11426, 11427, 11428, 11429, 11430, 11432, 11436, 11450, 11462, 11466, 11468, 11472, 11474, 11576, 11577, 11599, 11601, 11604, 11605, 11613, 11840, 12034, 12035, 12295, 12308, 12309, 12339, 12448, 12755, 12756, 20022, 20031, 42192, 42193, 42194, 42195, 42196, 42198, 42199, 42201, 42202, 42204, 42205, 42207, 42208, 42209, 42210, 42211, 42214, 42215, 42218, 42219, 42220, 42222, 42224, 42226, 42227, 42228, 42232, 42233, 42237, 42239, 42510, 42564, 42567, 42719, 42731, 42735, 42801, 42842, 42858, 42862, 42872, 42889, 42892, 42904, 42905, 42911, 42923, 42930, 42931, 42932, 43826, 43829, 43837, 43847, 43848, 43854, 43858, 43866, 43893, 43905, 43907, 43923, 43945, 43946, 43951, 64422, 64423, 64424, 64425, 64426, 64427, 64428, 64429, 64830, 64831, 65072, 65101, 65102, 65103, 65112, 65128, 65165, 65166, 65257, 65258, 65259, 65260, 65282, 65284, 65285, 65286, 65287, 65290, 65291, 65293, 65294, 65295, 65296, 65297, 65298, 65299, 65300, 65301, 65302, 65303, 65304, 65305, 65308, 65309, 65310, 65312, 65313, 65314, 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, 65339, 65340, 65341, 65342, 65343, 65344, 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370, 65371, 65372, 65373, 65512, 66178, 66182, 66183, 66186, 66192, 66194, 66197, 66198, 66199, 66203, 66208, 66209, 66210, 66213, 66219, 66224, 66225, 66226, 66228, 66255, 66293, 66305, 66306, 66313, 66321, 66325, 66327, 66330, 66335, 66336, 66338, 66564, 66581, 66587, 66592, 66604, 66621, 66632, 66740, 66754, 66766, 66770, 66794, 66806, 66835, 66838, 66840, 66844, 66845, 66853, 66854, 66855, 68176, 70864, 71430, 71434, 71438, 71439, 71840, 71842, 71843, 71844, 71846, 71849, 71852, 71854, 71855, 71858, 71861, 71864, 71867, 71868, 71872, 71873, 71874, 71875, 71876, 71878, 71880, 71882, 71884, 71893, 71894, 71895, 71896, 71900, 71904, 71909, 71910, 71913, 71916, 71919, 71922, 93960, 93962, 93974, 93992, 94005, 94010, 94011, 94015, 94016, 94018, 94019, 94033, 94034, 119060, 119149, 119302, 119309, 119311, 119314, 119315, 119318, 119338, 119350, 119351, 119354, 119355, 119808, 119809, 119810, 119811, 119812, 119813, 119814, 119815, 119816, 119817, 119818, 119819, 119820, 119821, 119822, 119823, 119824, 119825, 119826, 119827, 119828, 119829, 119830, 119831, 119832, 119833, 119834, 119835, 119836, 119837, 119838, 119839, 119840, 119841, 119842, 119843, 119844, 119845, 119847, 119848, 119849, 119850, 119851, 119852, 119853, 119854, 119855, 119856, 119857, 119858, 119859, 119860, 119861, 119862, 119863, 119864, 119865, 119866, 119867, 119868, 119869, 119870, 119871, 119872, 119873, 119874, 119875, 119876, 119877, 119878, 119879, 119880, 119881, 119882, 119883, 119884, 119885, 119886, 119887, 119888, 119889, 119890, 119891, 119892, 119894, 119895, 119896, 119897, 119899, 119900, 119901, 119902, 119903, 119904, 119905, 119906, 119907, 119908, 119909, 119910, 119911, 119912, 119913, 119914, 119915, 119916, 119917, 119918, 119919, 119920, 119921, 119922, 119923, 119924, 119925, 119926, 119927, 119928, 119929, 119930, 119931, 119932, 119933, 119934, 119935, 119936, 119937, 119938, 119939, 119940, 119941, 119942, 119943, 119944, 119945, 119946, 119947, 119948, 119949, 119951, 119952, 119953, 119954, 119955, 119956, 119957, 119958, 119959, 119960, 119961, 119962, 119963, 119964, 119966, 119967, 119970, 119973, 119974, 119977, 119978, 119979, 119980, 119982, 119983, 119984, 119985, 119986, 119987, 119988, 119989, 119990, 119991, 119992, 119993, 119995, 119997, 119998, 119999, 120000, 120001, 120003, 120005, 120006, 120007, 120008, 120009, 120010, 120011, 120012, 120013, 120014, 120015, 120016, 120017, 120018, 120019, 120020, 120021, 120022, 120023, 120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031, 120032, 120033, 120034, 120035, 120036, 120037, 120038, 120039, 120040, 120041, 120042, 120043, 120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051, 120052, 120053, 120055, 120056, 120057, 120058, 120059, 120060, 120061, 120062, 120063, 120064, 120065, 120066, 120067, 120068, 120069, 120071, 120072, 120073, 120074, 120077, 120078, 120079, 120080, 120081, 120082, 120083, 120084, 120086, 120087, 120088, 120089, 120090, 120091, 120092, 120094, 120095, 120096, 120097, 120098, 120099, 120100, 120101, 120102, 120103, 120104, 120105, 120107, 120108, 120109, 120110, 120111, 120112, 120113, 120114, 120115, 120116, 120117, 120118, 120119, 120120, 120121, 120123, 120124, 120125, 120126, 120128, 120129, 120130, 120131, 120132, 120134, 120138, 120139, 120140, 120141, 120142, 120143, 120144, 120146, 120147, 120148, 120149, 120150, 120151, 120152, 120153, 120154, 120155, 120156, 120157, 120159, 120160, 120161, 120162, 120163, 120164, 120165, 120166, 120167, 120168, 120169, 120170, 120171, 120172, 120173, 120174, 120175, 120176, 120177, 120178, 120179, 120180, 120181, 120182, 120183, 120184, 120185, 120186, 120187, 120188, 120189, 120190, 120191, 120192, 120193, 120194, 120195, 120196, 120197, 120198, 120199, 120200, 120201, 120202, 120203, 120204, 120205, 120206, 120207, 120208, 120209, 120211, 120212, 120213, 120214, 120215, 120216, 120217, 120218, 120219, 120220, 120221, 120222, 120223, 120224, 120225, 120226, 120227, 120228, 120229, 120230, 120231, 120232, 120233, 120234, 120235, 120236, 120237, 120238, 120239, 120240, 120241, 120242, 120243, 120244, 120245, 120246, 120247, 120248, 120249, 120250, 120251, 120252, 120253, 120254, 120255, 120256, 120257, 120258, 120259, 120260, 120261, 120263, 120264, 120265, 120266, 120267, 120268, 120269, 120270, 120271, 120272, 120273, 120274, 120275, 120276, 120277, 120278, 120279, 120280, 120281, 120282, 120283, 120284, 120285, 120286, 120287, 120288, 120289, 120290, 120291, 120292, 120293, 120294, 120295, 120296, 120297, 120298, 120299, 120300, 120301, 120302, 120303, 120304, 120305, 120306, 120307, 120308, 120309, 120310, 120311, 120312, 120313, 120315, 120316, 120317, 120318, 120319, 120320, 120321, 120322, 120323, 120324, 120325, 120326, 120327, 120328, 120329, 120330, 120331, 120332, 120333, 120334, 120335, 120336, 120337, 120338, 120339, 120340, 120341, 120342, 120343, 120344, 120345, 120346, 120347, 120348, 120349, 120350, 120351, 120352, 120353, 120354, 120355, 120356, 120357, 120358, 120359, 120360, 120361, 120362, 120363, 120364, 120365, 120367, 120368, 120369, 120370, 120371, 120372, 120373, 120374, 120375, 120376, 120377, 120378, 120379, 120380, 120381, 120382, 120383, 120384, 120385, 120386, 120387, 120388, 120389, 120390, 120391, 120392, 120393, 120394, 120395, 120396, 120397, 120398, 120399, 120400, 120401, 120402, 120403, 120404, 120405, 120406, 120407, 120408, 120409, 120410, 120411, 120412, 120413, 120414, 120415, 120416, 120417, 120419, 120420, 120421, 120422, 120423, 120424, 120425, 120426, 120427, 120428, 120429, 120430, 120431, 120432, 120433, 120434, 120435, 120436, 120437, 120438, 120439, 120440, 120441, 120442, 120443, 120444, 120445, 120446, 120447, 120448, 120449, 120450, 120451, 120452, 120453, 120454, 120455, 120456, 120457, 120458, 120459, 120460, 120461, 120462, 120463, 120464, 120465, 120466, 120467, 120468, 120469, 120471, 120472, 120473, 120474, 120475, 120476, 120477, 120478, 120479, 120480, 120481, 120482, 120483, 120484, 120488, 120489, 120492, 120493, 120494, 120496, 120497, 120499, 120500, 120502, 120504, 120507, 120508, 120510, 120514, 120516, 120522, 120526, 120528, 120530, 120532, 120534, 120544, 120546, 120547, 120550, 120551, 120552, 120554, 120555, 120557, 120558, 120560, 120562, 120565, 120566, 120568, 120572, 120574, 120580, 120584, 120586, 120588, 120590, 120592, 120602, 120604, 120605, 120608, 120609, 120610, 120612, 120613, 120615, 120616, 120618, 120620, 120623, 120624, 120626, 120630, 120632, 120638, 120642, 120644, 120646, 120648, 120650, 120660, 120662, 120663, 120666, 120667, 120668, 120670, 120671, 120673, 120674, 120676, 120678, 120681, 120682, 120684, 120688, 120690, 120696, 120700, 120702, 120704, 120706, 120708, 120718, 120720, 120721, 120724, 120725, 120726, 120728, 120729, 120731, 120732, 120734, 120736, 120739, 120740, 120742, 120746, 120748, 120754, 120758, 120760, 120762, 120764, 120766, 120776, 120778, 120782, 120783, 120784, 120785, 120786, 120787, 120788, 120789, 120790, 120791, 120792, 120793, 120794, 120795, 120796, 120797, 120798, 120799, 120800, 120801, 120802, 120803, 120804, 120805, 120806, 120807, 120808, 120809, 120810, 120811, 120812, 120813, 120814, 120815, 120816, 120817, 120818, 120819, 120820, 120821, 120822, 120823, 120824, 120825, 120826, 120827, 120828, 120829, 120830, 120831, 125127, 125131, 126464, 126500, 126564, 126592, 126596, 128844, 128872, 130032, 130033, 130034, 130035, 130036, 130037, 130038, 130039, 130040, 130041},
+		With:       []rune{44, 102, 98, 103, 82, 50, 51, 53, 115, 73, 33, 51, 56, 56, 63, 97, 103, 121, 105, 105, 119, 117, 121, 63, 96, 96, 96, 96, 96, 60, 62, 94, 94, 96, 96, 96, 58, 45, 105, 126, 96, 58, 96, 105, 59, 74, 96, 65, 66, 69, 90, 72, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 89, 70, 50, 99, 106, 67, 77, 83, 73, 74, 115, 105, 106, 119, 86, 118, 89, 121, 104, 101, 73, 105, 51, 100, 71, 113, 87, 119, 85, 83, 79, 96, 96, 119, 113, 113, 104, 110, 110, 117, 103, 102, 111, 58, 108, 58, 108, 118, 96, 108, 111, 96, 44, 108, 111, 46, 108, 111, 86, 44, 42, 111, 111, 45, 111, 46, 73, 111, 86, 46, 46, 58, 58, 79, 108, 96, 96, 95, 58, 111, 63, 79, 56, 57, 111, 57, 56, 58, 111, 56, 79, 79, 57, 111, 111, 111, 111, 111, 111, 111, 111, 57, 111, 111, 111, 111, 111, 121, 111, 85, 79, 68, 82, 84, 105, 89, 65, 74, 69, 63, 87, 77, 72, 89, 71, 104, 90, 52, 98, 82, 87, 83, 86, 83, 76, 67, 80, 75, 100, 54, 71, 66, 61, 86, 62, 60, 96, 85, 80, 100, 98, 74, 76, 50, 120, 72, 120, 82, 98, 70, 65, 68, 68, 77, 66, 88, 120, 32, 60, 88, 73, 96, 75, 77, 58, 43, 47, 58, 58, 99, 111, 111, 117, 118, 119, 122, 114, 103, 121, 102, 121, 96, 105, 96, 126, 96, 96, 96, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 44, 96, 46, 32, 32, 32, 96, 60, 62, 47, 45, 47, 42, 126, 58, 32, 67, 103, 72, 72, 72, 104, 73, 73, 76, 108, 78, 80, 81, 82, 82, 82, 90, 90, 75, 66, 67, 101, 101, 69, 70, 77, 111, 105, 121, 68, 100, 101, 105, 106, 73, 86, 88, 76, 67, 68, 77, 105, 118, 120, 73, 99, 100, 45, 47, 92, 42, 73, 118, 85, 58, 126, 84, 118, 85, 69, 105, 112, 97, 73, 47, 88, 40, 41, 60, 62, 40, 41, 123, 125, 43, 45, 47, 92, 84, 120, 120, 92, 47, 92, 120, 114, 72, 73, 75, 77, 78, 79, 111, 80, 112, 67, 99, 84, 89, 88, 45, 47, 57, 51, 76, 54, 86, 69, 73, 33, 79, 81, 88, 61, 92, 47, 79, 40, 41, 47, 61, 47, 92, 92, 47, 66, 80, 100, 68, 84, 71, 75, 74, 67, 90, 70, 77, 78, 76, 83, 82, 86, 72, 87, 88, 89, 65, 69, 73, 79, 85, 46, 44, 58, 61, 46, 50, 105, 86, 63, 50, 115, 50, 51, 57, 38, 58, 96, 70, 102, 117, 51, 74, 88, 66, 101, 102, 111, 114, 114, 117, 117, 121, 105, 114, 119, 122, 118, 115, 99, 111, 111, 111, 111, 111, 111, 111, 111, 40, 41, 58, 95, 95, 95, 45, 92, 108, 108, 111, 111, 111, 111, 34, 36, 37, 38, 96, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 60, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 73, 66, 69, 70, 124, 88, 79, 80, 83, 84, 43, 65, 66, 67, 70, 79, 77, 84, 89, 88, 72, 90, 66, 67, 124, 77, 84, 88, 56, 42, 108, 88, 79, 67, 76, 83, 111, 99, 115, 82, 79, 85, 55, 111, 117, 78, 79, 75, 67, 86, 70, 76, 88, 46, 79, 118, 119, 119, 119, 86, 70, 76, 89, 69, 90, 57, 69, 52, 76, 79, 85, 53, 84, 118, 115, 70, 105, 122, 55, 111, 51, 57, 54, 57, 111, 117, 121, 79, 90, 87, 67, 88, 87, 67, 86, 84, 76, 73, 82, 83, 51, 62, 65, 85, 89, 96, 96, 123, 46, 51, 86, 92, 55, 70, 82, 76, 60, 62, 47, 92, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 67, 68, 71, 74, 75, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 102, 104, 105, 106, 107, 108, 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 73, 74, 75, 76, 77, 79, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 105, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 70, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 108, 56, 108, 111, 111, 108, 111, 67, 84, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57},
+		Locale:     "_common",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 184, Hi: 383, Stride: 199},
+				{Lo: 388, Hi: 397, Stride: 9},
+				{Lo: 422, Hi: 423, Stride: 1},
+				{Lo: 439, Hi: 444, Stride: 5},
+				{Lo: 445, Hi: 451, Stride: 3},
+				{Lo: 540, Hi: 546, Stride: 6},
+				{Lo: 547, Hi: 577, Stride: 30},
+				{Lo: 593, Hi: 609, Stride: 16},
+				{Lo: 611, Hi: 617, Stride: 6},
+				{Lo: 618, Hi: 623, Stride: 5},
+				{Lo: 651, Hi: 655, Stride: 4},
+				{Lo: 660, Hi: 697, Stride: 37},
+				{Lo: 699, Hi: 702, Stride: 1},
+				{Lo: 706, Hi: 708, Stride: 1},
+				{Lo: 710, Hi: 714, Stride: 2},
+				{Lo: 715, Hi: 720, Stride: 5},
+				{Lo: 727, Hi: 731, Stride: 4},
+				{Lo: 732, Hi: 756, Stride: 24},
+				{Lo: 760, Hi: 884, Stride: 124},
+				{Lo: 890, Hi: 894, Stride: 4},
+				{Lo: 895, Hi: 900, Stride: 5},
+				{Lo: 913, Hi: 914, Stride: 1},
+				{Lo: 917, Hi: 919, Stride: 1},
+				{Lo: 922, Hi: 924, Stride: 2},
+				{Lo: 925, Hi: 929, Stride: 2},
+				{Lo: 932, Hi: 933, Stride: 1},
+				{Lo: 935, Hi: 945, Stride: 10},
+				{Lo: 947, Hi: 953, Stride: 6},
+				{Lo: 957, Hi: 965, Stride: 2},
+				{Lo: 978, Hi: 988, Stride: 10},
+				{Lo: 1000, Hi: 1010, Stride: 10},
+				{Lo: 1011, Hi: 1017, Stride: 6},
+				{Lo: 1018, Hi: 1029, Stride: 11},
+				{Lo: 1030, Hi: 1032, Stride: 2},
+				{Lo: 1109, Hi: 1110, Stride: 1},
+				{Lo: 1112, Hi: 1121, Stride: 9},
+				{Lo: 1140, Hi: 1141, Stride: 1},
+				{Lo: 1198, Hi: 1199, Stride: 1},
+				{Lo: 1211, Hi: 1213, Stride: 2},
+				{Lo: 1216, Hi: 1231, Stride: 15},
+				{Lo: 1248, Hi: 1281, Stride: 33},
+				{Lo: 1292, Hi: 1307, Stride: 15},
+				{Lo: 1308, Hi: 1309, Stride: 1},
+				{Lo: 1357, Hi: 1359, Stride: 2},
+				{Lo: 1365, Hi: 1370, Stride: 5},
+				{Lo: 1373, Hi: 1377, Stride: 4},
+				{Lo: 1379, Hi: 1382, Stride: 3},
+				{Lo: 1392, Hi: 1400, Stride: 8},
+				{Lo: 1404, Hi: 1405, Stride: 1},
+				{Lo: 1409, Hi: 1412, Stride: 3},
+				{Lo: 1413, Hi: 1417, Stride: 4},
+				{Lo: 1472, Hi: 1475, Stride: 3},
+				{Lo: 1493, Hi: 1496, Stride: 3},
+				{Lo: 1497, Hi: 1503, Stride: 6},
+				{Lo: 1505, Hi: 1523, Stride: 18},
+				{Lo: 1549, Hi: 1575, Stride: 26},
+				{Lo: 1607, Hi: 1632, Stride: 25},
+				{Lo: 1633, Hi: 1637, Stride: 4},
+				{Lo: 1639, Hi: 1643, Stride: 4},
+				{Lo: 1645, Hi: 1726, Stride: 81},
+				{Lo: 1729, Hi: 1748, Stride: 19},
+				{Lo: 1749, Hi: 1776, Stride: 27},
+				{Lo: 1777, Hi: 1781, Stride: 4},
+				{Lo: 1783, Hi: 1793, Stride: 10},
+				{Lo: 1794, Hi: 1796, Stride: 1},
+				{Lo: 1984, Hi: 1994, Stride: 10},
+				{Lo: 2036, Hi: 2037, Stride: 1},
+				{Lo: 2042, Hi: 2307, Stride: 265},
+				{Lo: 2406, Hi: 2429, Stride: 23},
+				{Lo: 2534, Hi: 2538, Stride: 4},
+				{Lo: 2541, Hi: 2662, Stride: 121},
+				{Lo: 2663, Hi: 2666, Stride: 3},
+				{Lo: 2691, Hi: 2790, Stride: 99},
+				{Lo: 2819, Hi: 2848, Stride: 29},
+				{Lo: 2918, Hi: 2920, Stride: 2},
+				{Lo: 3046, Hi: 3074, Stride: 28},
+				{Lo: 3174, Hi: 3202, Stride: 28},
+				{Lo: 3302, Hi: 3330, Stride: 28},
+				{Lo: 3360, Hi: 3430, Stride: 70},
+				{Lo: 3437, Hi: 3458, Stride: 21},
+				{Lo: 3664, Hi: 3792, Stride: 128},
+				{Lo: 4125, Hi: 4160, Stride: 35},
+				{Lo: 4327, Hi: 4351, Stride: 24},
+				{Lo: 4608, Hi: 5024, Stride: 208},
+				{Lo: 5025, Hi: 5026, Stride: 1},
+				{Lo: 5029, Hi: 5033, Stride: 4},
+				{Lo: 5034, Hi: 5036, Stride: 1},
+				{Lo: 5038, Hi: 5043, Stride: 5},
+				{Lo: 5047, Hi: 5051, Stride: 4},
+				{Lo: 5053, Hi: 5056, Stride: 3},
+				{Lo: 5058, Hi: 5059, Stride: 1},
+				{Lo: 5070, Hi: 5071, Stride: 1},
+				{Lo: 5074, Hi: 5076, Stride: 2},
+				{Lo: 5077, Hi: 5081, Stride: 4},
+				{Lo: 5082, Hi: 5086, Stride: 4},
+				{Lo: 5087, Hi: 5090, Stride: 3},
+				{Lo: 5094, Hi: 5095, Stride: 1},
+				{Lo: 5102, Hi: 5107, Stride: 5},
+				{Lo: 5108, Hi: 5120, Stride: 12},
+				{Lo: 5167, Hi: 5171, Stride: 4},
+				{Lo: 5176, Hi: 5194, Stride: 18},
+				{Lo: 5196, Hi: 5229, Stride: 33},
+				{Lo: 5231, Hi: 5234, Stride: 3},
+				{Lo: 5261, Hi: 5290, Stride: 29},
+				{Lo: 5311, Hi: 5441, Stride: 130},
+				{Lo: 5500, Hi: 5501, Stride: 1},
+				{Lo: 5511, Hi: 5551, Stride: 40},
+				{Lo: 5556, Hi: 5573, Stride: 17},
+				{Lo: 5598, Hi: 5610, Stride: 12},
+				{Lo: 5616, Hi: 5623, Stride: 7},
+				{Lo: 5741, Hi: 5742, Stride: 1},
+				{Lo: 5760, Hi: 5810, Stride: 50},
+				{Lo: 5815, Hi: 5825, Stride: 10},
+				{Lo: 5836, Hi: 5845, Stride: 9},
+				{Lo: 5846, Hi: 5868, Stride: 22},
+				{Lo: 5869, Hi: 5941, Stride: 72},
+				{Lo: 6147, Hi: 6153, Stride: 6},
+				{Lo: 7428, Hi: 7439, Stride: 11},
+				{Lo: 7441, Hi: 7452, Stride: 11},
+				{Lo: 7456, Hi: 7458, Stride: 1},
+				{Lo: 7462, Hi: 7555, Stride: 93},
+				{Lo: 7564, Hi: 7837, Stride: 273},
+				{Lo: 7935, Hi: 8125, Stride: 190},
+				{Lo: 8126, Hi: 8128, Stride: 1},
+				{Lo: 8175, Hi: 8189, Stride: 14},
+				{Lo: 8190, Hi: 8192, Stride: 2},
+				{Lo: 8193, Hi: 8202, Stride: 1},
+				{Lo: 8208, Hi: 8210, Stride: 1},
+				{Lo: 8218, Hi: 8219, Stride: 1},
+				{Lo: 8228, Hi: 8232, Stride: 4},
+				{Lo: 8233, Hi: 8239, Stride: 6},
+				{Lo: 8242, Hi: 8249, Stride: 7},
+				{Lo: 8250, Hi: 8257, Stride: 7},
+				{Lo: 8259, Hi: 8260, Stride: 1},
+				{Lo: 8270, Hi: 8275, Stride: 5},
+				{Lo: 8282, Hi: 8287, Stride: 5},
+				{Lo: 8450, Hi: 8458, Stride: 8},
+				{Lo: 8459, Hi: 8462, Stride: 1},
+				{Lo: 8464, Hi: 8467, Stride: 1},
+				{Lo: 8469, Hi: 8473, Stride: 4},
+				{Lo: 8474, Hi: 8477, Stride: 1},
+				{Lo: 8484, Hi: 8488, Stride: 4},
+				{Lo: 8490, Hi: 8492, Stride: 2},
+				{Lo: 8493, Hi: 8497, Stride: 1},
+				{Lo: 8499, Hi: 8500, Stride: 1},
+				{Lo: 8505, Hi: 8509, Stride: 4},
+				{Lo: 8517, Hi: 8521, Stride: 1},
+				{Lo: 8544, Hi: 8548, Stride: 4},
+				{Lo: 8553, Hi: 8556, Stride: 3},
+				{Lo: 8557, Hi: 8560, Stride: 1},
+				{Lo: 8564, Hi: 8569, Stride: 5},
+				{Lo: 8572, Hi: 8574, Stride: 1},
+				{Lo: 8722, Hi: 8725, Stride: 3},
+				{Lo: 8726, Hi: 8727, Stride: 1},
+				{Lo: 8739, Hi: 8744, Stride: 5},
+				{Lo: 8746, Hi: 8758, Stride: 12},
+				{Lo: 8764, Hi: 8868, Stride: 104},
+				{Lo: 8897, Hi: 8899, Stride: 2},
+				{Lo: 8959, Hi: 9075, Stride: 116},
+				{Lo: 9076, Hi: 9082, Stride: 6},
+				{Lo: 9213, Hi: 9585, Stride: 372},
+				{Lo: 9587, Hi: 10088, Stride: 501},
+				{Lo: 10089, Hi: 10094, Stride: 5},
+				{Lo: 10095, Hi: 10098, Stride: 3},
+				{Lo: 10099, Hi: 10101, Stride: 1},
+				{Lo: 10133, Hi: 10134, Stride: 1},
+				{Lo: 10187, Hi: 10189, Stride: 2},
+				{Lo: 10201, Hi: 10539, Stride: 338},
+				{Lo: 10540, Hi: 10741, Stride: 201},
+				{Lo: 10744, Hi: 10745, Stride: 1},
+				{Lo: 10799, Hi: 11397, Stride: 598},
+				{Lo: 11406, Hi: 11410, Stride: 4},
+				{Lo: 11412, Hi: 11416, Stride: 4},
+				{Lo: 11418, Hi: 11422, Stride: 4},
+				{Lo: 11423, Hi: 11426, Stride: 3},
+				{Lo: 11427, Hi: 11430, Stride: 1},
+				{Lo: 11432, Hi: 11436, Stride: 4},
+				{Lo: 11450, Hi: 11462, Stride: 12},
+				{Lo: 11466, Hi: 11468, Stride: 2},
+				{Lo: 11472, Hi: 11474, Stride: 2},
+				{Lo: 11576, Hi: 11577, Stride: 1},
+				{Lo: 11599, Hi: 11601, Stride: 2},
+				{Lo: 11604, Hi: 11605, Stride: 1},
+				{Lo: 11613, Hi: 11840, Stride: 227},
+				{Lo: 12034, Hi: 12035, Stride: 1},
+				{Lo: 12295, Hi: 12308, Stride: 13},
+				{Lo: 12309, Hi: 12339, Stride: 30},
+				{Lo: 12448, Hi: 12755, Stride: 307},
+				{Lo: 12756, Hi: 20022, Stride: 7266},
+				{Lo: 20031, Hi: 42192, Stride: 22161},
+				{Lo: 42193, Hi: 42196, Stride: 1},
+				{Lo: 42198, Hi: 42199, Stride: 1},
+				{Lo: 42201, Hi: 42202, Stride: 1},
+				{Lo: 42204, Hi: 42205, Stride: 1},
+				{Lo: 42207, Hi: 42211, Stride: 1},
+				{Lo: 42214, Hi: 42215, Stride: 1},
+				{Lo: 42218, Hi: 42220, Stride: 1},
+				{Lo: 42222, Hi: 42226, Stride: 2},
+				{Lo: 42227, Hi: 42228, Stride: 1},
+				{Lo: 42232, Hi: 42233, Stride: 1},
+				{Lo: 42237, Hi: 42239, Stride: 2},
+				{Lo: 42510, Hi: 42564, Stride: 54},
+				{Lo: 42567, Hi: 42719, Stride: 152},
+				{Lo: 42731, Hi: 42735, Stride: 4},
+				{Lo: 42801, Hi: 42842, Stride: 41},
+				{Lo: 42858, Hi: 42862, Stride: 4},
+				{Lo: 42872, Hi: 42889, Stride: 17},
+				{Lo: 42892, Hi: 42904, Stride: 12},
+				{Lo: 42905, Hi: 42911, Stride: 6},
+				{Lo: 42923, Hi: 42930, Stride: 7},
+				{Lo: 42931, Hi: 42932, Stride: 1},
+				{Lo: 43826, Hi: 43829, Stride: 3},
+				{Lo: 43837, Hi: 43847, Stride: 10},
+				{Lo: 43848, Hi: 43854, Stride: 6},
+				{Lo: 43858, Hi: 43866, Stride: 8},
+				{Lo: 43893, Hi: 43905, Stride: 12},
+				{Lo: 43907, Hi: 43923, Stride: 16},
+				{Lo: 43945, Hi: 43946, Stride: 1},
+				{Lo: 43951, Hi: 64422, Stride: 20471},
+				{Lo: 64423, Hi: 64429, Stride: 1},
+				{Lo: 64830, Hi: 64831, Stride: 1},
+				{Lo: 65072, Hi: 65101, Stride: 29},
+				{Lo: 65102, Hi: 65103, Stride: 1},
+				{Lo: 65112, Hi: 65128, Stride: 16},
+				{Lo: 65165, Hi: 65166, Stride: 1},
+				{Lo: 65257, Hi: 65260, Stride: 1},
+				{Lo: 65282, Hi: 65284, Stride: 2},
+				{Lo: 65285, Hi: 65287, Stride: 1},
+				{Lo: 65290, Hi: 65291, Stride: 1},
+				{Lo: 65293, Hi: 65305, Stride: 1},
+				{Lo: 65308, Hi: 65310, Stride: 1},
+				{Lo: 65312, Hi: 65373, Stride: 1},
+				{Lo: 65512, Hi: 65512, Stride: 1},
+			},
+			R32: []unicode.Range32{
+				{Lo: 66178, Hi: 66182, Stride: 4},
+				{Lo: 66183, Hi: 66186, Stride: 3},
+				{Lo: 66192, Hi: 66194, Stride: 2},
+				{Lo: 66197, Hi: 66199, Stride: 1},
+				{Lo: 66203, Hi: 66208, Stride: 5},
+				{Lo: 66209, Hi: 66210, Stride: 1},
+				{Lo: 66213, Hi: 66219, Stride: 6},
+				{Lo: 66224, Hi: 66226, Stride: 1},
+				{Lo: 66228, Hi: 66255, Stride: 27},
+				{Lo: 66293, Hi: 66305, Stride: 12},
+				{Lo: 66306, Hi: 66313, Stride: 7},
+				{Lo: 66321, Hi: 66325, Stride: 4},
+				{Lo: 66327, Hi: 66330, Stride: 3},
+				{Lo: 66335, Hi: 66336, Stride: 1},
+				{Lo: 66338, Hi: 66564, Stride: 226},
+				{Lo: 66581, Hi: 66587, Stride: 6},
+				{Lo: 66592, Hi: 66604, Stride: 12},
+				{Lo: 66621, Hi: 66632, Stride: 11},
+				{Lo: 66740, Hi: 66754, Stride: 14},
+				{Lo: 66766, Hi: 66770, Stride: 4},
+				{Lo: 66794, Hi: 66806, Stride: 12},
+				{Lo: 66835, Hi: 66838, Stride: 3},
+				{Lo: 66840, Hi: 66844, Stride: 4},
+				{Lo: 66845, Hi: 66853, Stride: 8},
+				{Lo: 66854, Hi: 66855, Stride: 1},
+				{Lo: 68176, Hi: 70864, Stride: 2688},
+				{Lo: 71430, Hi: 71438, Stride: 4},
+				{Lo: 71439, Hi: 71840, Stride: 401},
+				{Lo: 71842, Hi: 71844, Stride: 1},
+				{Lo: 71846, Hi: 71852, Stride: 3},
+				{Lo: 71854, Hi: 71855, Stride: 1},
+				{Lo: 71858, Hi: 71867, Stride: 3},
+				{Lo: 71868, Hi: 71872, Stride: 4},
+				{Lo: 71873, Hi: 71876, Stride: 1},
+				{Lo: 71878, Hi: 71884, Stride: 2},
+				{Lo: 71893, Hi: 71896, Stride: 1},
+				{Lo: 71900, Hi: 71904, Stride: 4},
+				{Lo: 71909, Hi: 71910, Stride: 1},
+				{Lo: 71913, Hi: 71922, Stride: 3},
+				{Lo: 93960, Hi: 93962, Stride: 2},
+				{Lo: 93974, Hi: 93992, Stride: 18},
+				{Lo: 94005, Hi: 94010, Stride: 5},
+				{Lo: 94011, Hi: 94015, Stride: 4},
+				{Lo: 94016, Hi: 94018, Stride: 2},
+				{Lo: 94019, Hi: 94033, Stride: 14},
+				{Lo: 94034, Hi: 119060, Stride: 25026},
+				{Lo: 119149, Hi: 119302, Stride: 153},
+				{Lo: 119309, Hi: 119311, Stride: 2},
+				{Lo: 119314, Hi: 119315, Stride: 1},
+				{Lo: 119318, Hi: 119338, Stride: 20},
+				{Lo: 119350, Hi: 119351, Stride: 1},
+				{Lo: 119354, Hi: 119355, Stride: 1},
+				{Lo: 119808, Hi: 119845, Stride: 1},
+				{Lo: 119847, Hi: 119892, Stride: 1},
+				{Lo: 119894, Hi: 119897, Stride: 1},
+				{Lo: 119899, Hi: 119949, Stride: 1},
+				{Lo: 119951, Hi: 119964, Stride: 1},
+				{Lo: 119966, Hi: 119967, Stride: 1},
+				{Lo: 119970, Hi: 119973, Stride: 3},
+				{Lo: 119974, Hi: 119977, Stride: 3},
+				{Lo: 119978, Hi: 119980, Stride: 1},
+				{Lo: 119982, Hi: 119993, Stride: 1},
+				{Lo: 119995, Hi: 119997, Stride: 2},
+				{Lo: 119998, Hi: 120001, Stride: 1},
+				{Lo: 120003, Hi: 120005, Stride: 2},
+				{Lo: 120006, Hi: 120053, Stride: 1},
+				{Lo: 120055, Hi: 120069, Stride: 1},
+				{Lo: 120071, Hi: 120074, Stride: 1},
+				{Lo: 120077, Hi: 120084, Stride: 1},
+				{Lo: 120086, Hi: 120092, Stride: 1},
+				{Lo: 120094, Hi: 120105, Stride: 1},
+				{Lo: 120107, Hi: 120121, Stride: 1},
+				{Lo: 120123, Hi: 120126, Stride: 1},
+				{Lo: 120128, Hi: 120132, Stride: 1},
+				{Lo: 120134, Hi: 120138, Stride: 4},
+				{Lo: 120139, Hi: 120144, Stride: 1},
+				{Lo: 120146, Hi: 120157, Stride: 1},
+				{Lo: 120159, Hi: 120209, Stride: 1},
+				{Lo: 120211, Hi: 120261, Stride: 1},
+				{Lo: 120263, Hi: 120313, Stride: 1},
+				{Lo: 120315, Hi: 120365, Stride: 1},
+				{Lo: 120367, Hi: 120417, Stride: 1},
+				{Lo: 120419, Hi: 120469, Stride: 1},
+				{Lo: 120471, Hi: 120484, Stride: 1},
+				{Lo: 120488, Hi: 120489, Stride: 1},
+				{Lo: 120492, Hi: 120494, Stride: 1},
+				{Lo: 120496, Hi: 120497, Stride: 1},
+				{Lo: 120499, Hi: 120500, Stride: 1},
+				{Lo: 120502, Hi: 120504, Stride: 2},
+				{Lo: 120507, Hi: 120508, Stride: 1},
+				{Lo: 120510, Hi: 120514, Stride: 4},
+				{Lo: 120516, Hi: 120522, Stride: 6},
+				{Lo: 120526, Hi: 120534, Stride: 2},
+				{Lo: 120544, Hi: 120546, Stride: 2},
+				{Lo: 120547, Hi: 120550, Stride: 3},
+				{Lo: 120551, Hi: 120552, Stride: 1},
+				{Lo: 120554, Hi: 120555, Stride: 1},
+				{Lo: 120557, Hi: 120558, Stride: 1},
+				{Lo: 120560, Hi: 120562, Stride: 2},
+				{Lo: 120565, Hi: 120566, Stride: 1},
+				{Lo: 120568, Hi: 120572, Stride: 4},
+				{Lo: 120574, Hi: 120580, Stride: 6},
+				{Lo: 120584, Hi: 120592, Stride: 2},
+				{Lo: 120602, Hi: 120604, Stride: 2},
+				{Lo: 120605, Hi: 120608, Stride: 3},
+				{Lo: 120609, Hi: 120610, Stride: 1},
+				{Lo: 120612, Hi: 120613, Stride: 1},
+				{Lo: 120615, Hi: 120616, Stride: 1},
+				{Lo: 120618, Hi: 120620, Stride: 2},
+				{Lo: 120623, Hi: 120624, Stride: 1},
+				{Lo: 120626, Hi: 120630, Stride: 4},
+				{Lo: 120632, Hi: 120638, Stride: 6},
+				{Lo: 120642, Hi: 120650, Stride: 2},
+				{Lo: 120660, Hi: 120662, Stride: 2},
+				{Lo: 120663, Hi: 120666, Stride: 3},
+				{Lo: 120667, Hi: 120668, Stride: 1},
+				{Lo: 120670, Hi: 120671, Stride: 1},
+				{Lo: 120673, Hi: 120674, Stride: 1},
+				{Lo: 120676, Hi: 120678, Stride: 2},
+				{Lo: 120681, Hi: 120682, Stride: 1},
+				{Lo: 120684, Hi: 120688, Stride: 4},
+				{Lo: 120690, Hi: 120696, Stride: 6},
+				{Lo: 120700, Hi: 120708, Stride: 2},
+				{Lo: 120718, Hi: 120720, Stride: 2},
+				{Lo: 120721, Hi: 120724, Stride: 3},
+				{Lo: 120725, Hi: 120726, Stride: 1},
+				{Lo: 120728, Hi: 120729, Stride: 1},
+				{Lo: 120731, Hi: 120732, Stride: 1},
+				{Lo: 120734, Hi: 120736, Stride: 2},
+				{Lo: 120739, Hi: 120740, Stride: 1},
+				{Lo: 120742, Hi: 120746, Stride: 4},
+				{Lo: 120748, Hi: 120754, Stride: 6},
+				{Lo: 120758, Hi: 120766, Stride: 2},
+				{Lo: 120776, Hi: 120778, Stride: 2},
+				{Lo: 120782, Hi: 120831, Stride: 1},
+				{Lo: 125127, Hi: 125131, Stride: 4},
+				{Lo: 126464, Hi: 126500, Stride: 36},
+				{Lo: 126564, Hi: 126592, Stride: 28},
+				{Lo: 126596, Hi: 128844, Stride: 2248},
+				{Lo: 128872, Hi: 130032, Stride: 1160},
+				{Lo: 130033, Hi: 130041, Stride: 1},
+			},
+			LatinOffset: 0,
+		},
+	},
+	"_default": {
+		Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "_default",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 160, Hi: 180, Stride: 20},
+				{Lo: 215, Hi: 305, Stride: 90},
+				{Lo: 921, Hi: 1009, Stride: 88},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8216, Stride: 5},
+				{Lo: 8217, Hi: 8245, Stride: 28},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"cs": {
+		Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "cs",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 305, Stride: 125},
+				{Lo: 921, Hi: 1009, Stride: 88},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8216, Hi: 8217, Stride: 1},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65288, Hi: 65289, Stride: 1},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65311, Stride: 4},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 0,
+		},
+	},
+	"de": {
+		Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "de",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 305, Stride: 125},
+				{Lo: 921, Hi: 1009, Stride: 88},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8216, Hi: 8217, Stride: 1},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65288, Hi: 65289, Stride: 1},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65311, Stride: 4},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 0,
+		},
+	},
+	"es": {
+		Confusable: []rune{180, 215, 305, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 120, 105, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "es",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 1009, Stride: 704},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8245, Stride: 34},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"fr": {
+		Confusable: []rune{215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "fr",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 215, Hi: 305, Stride: 90},
+				{Lo: 921, Hi: 1009, Stride: 88},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8216, Hi: 8245, Stride: 29},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 0,
+		},
+	},
+	"it": {
+		Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "it",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 160, Hi: 180, Stride: 20},
+				{Lo: 215, Hi: 305, Stride: 90},
+				{Lo: 921, Hi: 1009, Stride: 88},
+				{Lo: 1040, Hi: 1042, Stride: 2},
+				{Lo: 1045, Hi: 1047, Stride: 2},
+				{Lo: 1050, Hi: 1052, Stride: 2},
+				{Lo: 1053, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8216, Stride: 5},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65288, Hi: 65289, Stride: 1},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65311, Stride: 4},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"ja": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 65281, 65283, 65292, 65306, 65307},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 33, 35, 44, 58, 59},
+		Locale:     "ja",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8216, Stride: 5},
+				{Lo: 8217, Hi: 8245, Stride: 28},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65307, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"ko": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "ko",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8245, Stride: 34},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"pl": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "pl",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8216, Hi: 8217, Stride: 1},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65288, Hi: 65289, Stride: 1},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65311, Stride: 4},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"pt-BR": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "pt-BR",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8216, Hi: 8217, Stride: 1},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65283, Stride: 2},
+				{Lo: 65288, Hi: 65289, Stride: 1},
+				{Lo: 65292, Hi: 65306, Stride: 14},
+				{Lo: 65307, Hi: 65311, Stride: 4},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"qps-ploc": {
+		Confusable: []rune{160, 180, 215, 305, 921, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{32, 96, 120, 105, 73, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "qps-ploc",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 160, Hi: 180, Stride: 20},
+				{Lo: 215, Hi: 305, Stride: 90},
+				{Lo: 921, Hi: 1040, Stride: 119},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8216, Stride: 5},
+				{Lo: 8217, Hi: 8245, Stride: 28},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"ru": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "ru",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 8216, Stride: 7207},
+				{Lo: 8217, Hi: 8245, Stride: 28},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"tr": {
+		Confusable: []rune{160, 180, 215, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374},
+		With:       []rune{32, 96, 120, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126},
+		Locale:     "tr",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 160, Hi: 180, Stride: 20},
+				{Lo: 215, Hi: 921, Stride: 706},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 8245, Stride: 34},
+				{Lo: 12494, Hi: 65281, Stride: 52787},
+				{Lo: 65283, Hi: 65288, Stride: 5},
+				{Lo: 65289, Hi: 65292, Stride: 3},
+				{Lo: 65306, Hi: 65307, Stride: 1},
+				{Lo: 65311, Hi: 65374, Stride: 63},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"zh-hans": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8245, 12494, 65281, 65288, 65289, 65306, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 47, 33, 40, 41, 58, 126},
+		Locale:     "zh-hans",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8245, Hi: 12494, Stride: 4249},
+				{Lo: 65281, Hi: 65288, Stride: 7},
+				{Lo: 65289, Hi: 65306, Stride: 17},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+	"zh-hant": {
+		Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 12494, 65283, 65307, 65374},
+		With:       []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 47, 35, 59, 126},
+		Locale:     "zh-hant",
+		RangeTable: &unicode.RangeTable{
+			R16: []unicode.Range16{
+				{Lo: 180, Hi: 215, Stride: 35},
+				{Lo: 305, Hi: 921, Stride: 616},
+				{Lo: 1009, Hi: 1040, Stride: 31},
+				{Lo: 1042, Hi: 1045, Stride: 3},
+				{Lo: 1047, Hi: 1050, Stride: 3},
+				{Lo: 1052, Hi: 1054, Stride: 1},
+				{Lo: 1056, Hi: 1059, Stride: 1},
+				{Lo: 1061, Hi: 1068, Stride: 7},
+				{Lo: 1072, Hi: 1073, Stride: 1},
+				{Lo: 1075, Hi: 1077, Stride: 2},
+				{Lo: 1086, Hi: 1088, Stride: 2},
+				{Lo: 1089, Hi: 1093, Stride: 2},
+				{Lo: 8211, Hi: 12494, Stride: 4283},
+				{Lo: 65283, Hi: 65307, Stride: 24},
+				{Lo: 65374, Hi: 65374, Stride: 1},
+			},
+			R32:         []unicode.Range32{},
+			LatinOffset: 1,
+		},
+	},
+}
diff --git a/modules/charset/ambiguous_gen_test.go b/modules/charset/ambiguous_gen_test.go
new file mode 100644
index 0000000000000..bd64e1c5b1c93
--- /dev/null
+++ b/modules/charset/ambiguous_gen_test.go
@@ -0,0 +1,32 @@
+// 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 charset
+
+import (
+	"sort"
+	"testing"
+	"unicode"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAmbiguousCharacters(t *testing.T) {
+	for locale, ambiguous := range AmbiguousCharacters {
+		assert.Equal(t, locale, ambiguous.Locale)
+		assert.Equal(t, len(ambiguous.Confusable), len(ambiguous.With))
+		assert.True(t, sort.SliceIsSorted(ambiguous.Confusable, func(i, j int) bool {
+			return ambiguous.Confusable[i] < ambiguous.Confusable[j]
+		}))
+
+		for _, confusable := range ambiguous.Confusable {
+			assert.True(t, unicode.Is(ambiguous.RangeTable, confusable))
+			i := sort.Search(len(ambiguous.Confusable), func(j int) bool {
+				return ambiguous.Confusable[j] >= confusable
+			})
+			found := i < len(ambiguous.Confusable) && ambiguous.Confusable[i] == confusable
+			assert.True(t, found, "%c is not in %d", confusable, i)
+		}
+	}
+}
diff --git a/modules/charset/breakwriter.go b/modules/charset/breakwriter.go
new file mode 100644
index 0000000000000..619826ff21b10
--- /dev/null
+++ b/modules/charset/breakwriter.go
@@ -0,0 +1,44 @@
+// 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 charset
+
+import (
+	"bytes"
+	"io"
+)
+
+// BreakWriter wraps an io.Writer to always write '\n' as '<%X> `, bs)
-	return
-}
+// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
+func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
+	sb := &strings.Builder{}
+	outputStream := &HTMLStreamerWriter{Writer: sb}
+	streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
 
-func writeEscaped(output io.Writer, r rune) (err error) {
-	_, err = fmt.Fprintf(output, `%c     י ו ו ן ס ס ס ס ס ` + "\u2067" + ` ` + "\u2066" + ` ` + "\u202e" + ` ` + "\u2066" + ` ` + "\u2069" + ` ` + "\u2066" + ` ` + "\u202e" + ` ` + "\u2066" + ` ` + "\u2069" + ` ` + "\u2066" + ` ` + "\u2067" + ` ` + "\u2066" + ` ` + "\u202e" + ` ` + "\u2066" + ` ` + "\u2069" + ` ` + "\u2066" + ` ` + "\u202e" + ` ` + "\u2066" + ` ` + "\u2069" + ` ` + "\u2066" + ` `+"\u001e"+` `+"\u0300"+` 
+	filtered := make([]rune, 0, len(InvisibleRunes))
+	for _, r := range InvisibleRunes {
+		if r == ' ' || r == '\t' || r == '\n' {
+			continue
+		}
+		filtered = append(filtered, r)
+	}
+
+	table := rangetable.New(filtered...)
+	if err := runTemplate(generatorTemplate, output, table); err != nil {
+		fatalf("Unable to run template: %v", err)
+	}
+}
+
+func runTemplate(t *template.Template, filename string, data interface{}) error {
+	buf := bytes.NewBuffer(nil)
+	if err := t.Execute(buf, data); err != nil {
+		return fmt.Errorf("unable to execute template: %w", err)
+	}
+	bs, err := format.Source(buf.Bytes())
+	if err != nil {
+		verbosef("Bad source:\n%s", buf.String())
+		return fmt.Errorf("unable to format source: %w", err)
+	}
+
+	old, err := os.ReadFile(filename)
+	if err != nil && !os.IsNotExist(err) {
+		return fmt.Errorf("failed to read old file %s because %w", filename, err)
+	} else if err == nil {
+		if bytes.Equal(bs, old) {
+			// files are the same don't rewrite it.
+			return nil
+		}
+	}
+
+	file, err := os.Create(filename)
+	if err != nil {
+		return fmt.Errorf("failed to create file %s because %w", filename, err)
+	}
+	defer file.Close()
+	_, err = file.Write(bs)
+	if err != nil {
+		return fmt.Errorf("unable to write generated source: %w", err)
+	}
+	return nil
+}
+
+var generatorTemplate = template.Must(template.New("invisibleTemplate").Parse(`// This file is generated by modules/charset/invisible/generate.go DO NOT EDIT
+// 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 charset
+
+import "unicode"
+
+var InvisibleRanges = &unicode.RangeTable{
+	R16: []unicode.Range16{
+{{range .R16 }}		{Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}},
+{{end}}	},
+	R32: []unicode.Range32{
+{{range .R32}}		{Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}},
+{{end}}	},
+	LatinOffset: {{.LatinOffset}},
+}
+`))
+
+func logf(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format+"\n", args...)
+}
+
+func verbosef(format string, args ...interface{}) {
+	if verbose {
+		logf(format, args...)
+	}
+}
+
+func fatalf(format string, args ...interface{}) {
+	logf("fatal: "+format+"\n", args...)
+	os.Exit(1)
+}
diff --git a/modules/charset/invisible_gen.go b/modules/charset/invisible_gen.go
new file mode 100644
index 0000000000000..b3bfebe0c0e08
--- /dev/null
+++ b/modules/charset/invisible_gen.go
@@ -0,0 +1,37 @@
+// This file is generated by modules/charset/invisible/generate.go DO NOT EDIT
+// 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 charset
+
+import "unicode"
+
+var InvisibleRanges = &unicode.RangeTable{
+	R16: []unicode.Range16{
+		{Lo: 11, Hi: 13, Stride: 1},
+		{Lo: 127, Hi: 160, Stride: 33},
+		{Lo: 173, Hi: 847, Stride: 674},
+		{Lo: 1564, Hi: 4447, Stride: 2883},
+		{Lo: 4448, Hi: 6068, Stride: 1620},
+		{Lo: 6069, Hi: 6155, Stride: 86},
+		{Lo: 6156, Hi: 6158, Stride: 1},
+		{Lo: 7355, Hi: 7356, Stride: 1},
+		{Lo: 8192, Hi: 8207, Stride: 1},
+		{Lo: 8234, Hi: 8239, Stride: 1},
+		{Lo: 8287, Hi: 8303, Stride: 1},
+		{Lo: 10240, Hi: 12288, Stride: 2048},
+		{Lo: 12644, Hi: 65024, Stride: 52380},
+		{Lo: 65025, Hi: 65039, Stride: 1},
+		{Lo: 65279, Hi: 65440, Stride: 161},
+		{Lo: 65520, Hi: 65528, Stride: 1},
+		{Lo: 65532, Hi: 65532, Stride: 1},
+	},
+	R32: []unicode.Range32{
+		{Lo: 78844, Hi: 119155, Stride: 40311},
+		{Lo: 119156, Hi: 119162, Stride: 1},
+		{Lo: 917504, Hi: 917631, Stride: 1},
+		{Lo: 917760, Hi: 917999, Stride: 1},
+	},
+	LatinOffset: 2,
+}
diff --git a/modules/container/map.go b/modules/container/map.go
deleted file mode 100644
index 3519de09512e6..0000000000000
--- a/modules/container/map.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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 container
-
-// KeysInt64 returns keys slice for a map with int64 key
-func KeysInt64(m map[int64]struct{}) []int64 {
-	keys := make([]int64, 0, len(m))
-	for k := range m {
-		keys = append(keys, k)
-	}
-	return keys
-}
diff --git a/modules/container/set.go b/modules/container/set.go
new file mode 100644
index 0000000000000..4b4c74525d1fb
--- /dev/null
+++ b/modules/container/set.go
@@ -0,0 +1,57 @@
+// 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 container
+
+type Set[T comparable] map[T]struct{}
+
+// SetOf creates a set and adds the specified elements to it.
+func SetOf[T comparable](values ...T) Set[T] {
+	s := make(Set[T], len(values))
+	s.AddMultiple(values...)
+	return s
+}
+
+// Add adds the specified element to a set.
+// Returns true if the element is added; false if the element is already present.
+func (s Set[T]) Add(value T) bool {
+	if _, has := s[value]; !has {
+		s[value] = struct{}{}
+		return true
+	}
+	return false
+}
+
+// AddMultiple adds the specified elements to a set.
+func (s Set[T]) AddMultiple(values ...T) {
+	for _, value := range values {
+		s.Add(value)
+	}
+}
+
+// Contains determines whether a set contains the specified element.
+// Returns true if the set contains the specified element; otherwise, false.
+func (s Set[T]) Contains(value T) bool {
+	_, has := s[value]
+	return has
+}
+
+// Remove removes the specified element.
+// Returns true if the element is successfully found and removed; otherwise, false.
+func (s Set[T]) Remove(value T) bool {
+	if _, has := s[value]; has {
+		delete(s, value)
+		return true
+	}
+	return false
+}
+
+// Values gets a list of all elements in the set.
+func (s Set[T]) Values() []T {
+	keys := make([]T, 0, len(s))
+	for k := range s {
+		keys = append(keys, k)
+	}
+	return keys
+}
diff --git a/modules/container/set_test.go b/modules/container/set_test.go
new file mode 100644
index 0000000000000..6654763e56f36
--- /dev/null
+++ b/modules/container/set_test.go
@@ -0,0 +1,37 @@
+// 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 container
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSet(t *testing.T) {
+	s := make(Set[string])
+
+	assert.True(t, s.Add("key1"))
+	assert.False(t, s.Add("key1"))
+	assert.True(t, s.Add("key2"))
+
+	assert.True(t, s.Contains("key1"))
+	assert.True(t, s.Contains("key2"))
+	assert.False(t, s.Contains("key3"))
+
+	assert.True(t, s.Remove("key2"))
+	assert.False(t, s.Contains("key2"))
+
+	assert.False(t, s.Remove("key3"))
+
+	s.AddMultiple("key4", "key5")
+	assert.True(t, s.Contains("key4"))
+	assert.True(t, s.Contains("key5"))
+
+	s = SetOf("key6", "key7")
+	assert.False(t, s.Contains("key1"))
+	assert.True(t, s.Contains("key6"))
+	assert.True(t, s.Contains("key7"))
+}
diff --git a/modules/context/api.go b/modules/context/api.go
index 33534dbf6bf49..d95e58032177b 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -16,6 +16,7 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/web/middleware"
@@ -219,7 +220,13 @@ func (ctx *APIContext) CheckForOTP() {
 func APIAuth(authMethod auth_service.Method) func(*APIContext) {
 	return func(ctx *APIContext) {
 		// Get user from session if logged in.
-		ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
+		var err error
+		ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
+		if err != nil {
+			ctx.Error(http.StatusUnauthorized, "APIAuth", err)
+			return
+		}
+
 		if ctx.Doer != nil {
 			if ctx.Locale.Language() != ctx.Doer.Language {
 				ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
@@ -268,6 +275,7 @@ func APIContexter() func(http.Handler) http.Handler {
 				}
 			}
 
+			httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
 			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 			ctx.Data["Context"] = &ctx
@@ -340,7 +348,7 @@ func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context
 			}
 		}
 
-		return
+		return cancel
 	}
 }
 
@@ -386,7 +394,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
 				return
 			}
 			ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
-		} else if len(refName) == 40 {
+		} else if len(refName) == git.SHAFullLength {
 			ctx.Repo.CommitID = refName
 			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
 			if err != nil {
diff --git a/modules/context/auth.go b/modules/context/auth.go
index 09c22954550c7..e6d882eb5b34e 100644
--- a/modules/context/auth.go
+++ b/modules/context/auth.go
@@ -7,6 +7,7 @@ package context
 
 import (
 	"net/http"
+	"strings"
 
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/modules/log"
@@ -41,6 +42,10 @@ func Toggle(options *ToggleOptions) func(ctx *Context) {
 
 			if ctx.Doer.MustChangePassword {
 				if ctx.Req.URL.Path != "/user/settings/change_password" {
+					if strings.HasPrefix(ctx.Req.UserAgent(), "git") {
+						ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password"))
+						return
+					}
 					ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
 					ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
 					if ctx.Req.URL.Path != "/user/events" {
diff --git a/modules/context/context.go b/modules/context/context.go
index dcc43973ca244..b761f7b8035ff 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -28,11 +28,13 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	mc "code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/translation"
+	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web/middleware"
 	"code.gitea.io/gitea/services/auth"
@@ -223,7 +225,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
 	ctx.Data["TemplateLoadTimes"] = func() string {
 		return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
 	}
-	if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
+	if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
 		if status == http.StatusInternalServerError && name == base.TplName("status/500") {
 			ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
 			return
@@ -321,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
 	if statusPrefix == 4 || statusPrefix == 5 {
 		log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
 	}
-	ctx.Resp.WriteHeader(status)
 	ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 	ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
+	ctx.Resp.WriteHeader(status)
 	if _, err := ctx.Resp.Write(bs); err != nil {
 		log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
 	}
@@ -344,52 +346,63 @@ func (ctx *Context) RespHeader() http.Header {
 	return ctx.Resp.Header()
 }
 
-// SetServeHeaders sets necessary content serve headers
-func (ctx *Context) SetServeHeaders(filename string) {
-	ctx.Resp.Header().Set("Content-Description", "File Transfer")
-	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
-	ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+filename)
-	ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
-	ctx.Resp.Header().Set("Expires", "0")
-	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
-	ctx.Resp.Header().Set("Pragma", "public")
-	ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
+type ServeHeaderOptions struct {
+	ContentType        string // defaults to "application/octet-stream"
+	ContentTypeCharset string
+	ContentLength      *int64
+	Disposition        string // defaults to "attachment"
+	Filename           string
+	CacheDuration      time.Duration // defaults to 5 minutes
+	LastModified       time.Time
 }
 
-// ServeContent serves content to http request
-func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
-	modTime := time.Now()
-	for _, p := range params {
-		switch v := p.(type) {
-		case time.Time:
-			modTime = v
+// SetServeHeaders sets necessary content serve headers
+func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
+	header := ctx.Resp.Header()
+
+	contentType := typesniffer.ApplicationOctetStream
+	if opts.ContentType != "" {
+		if opts.ContentTypeCharset != "" {
+			contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
+		} else {
+			contentType = opts.ContentType
 		}
 	}
-	ctx.SetServeHeaders(name)
-	http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
-}
+	header.Set("Content-Type", contentType)
+	header.Set("X-Content-Type-Options", "nosniff")
 
-// ServeFile serves given file to response.
-func (ctx *Context) ServeFile(file string, names ...string) {
-	var name string
-	if len(names) > 0 {
-		name = names[0]
-	} else {
-		name = path.Base(file)
+	if opts.ContentLength != nil {
+		header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
 	}
-	ctx.SetServeHeaders(name)
-	http.ServeFile(ctx.Resp, ctx.Req, file)
-}
 
-// ServeStream serves file via io stream
-func (ctx *Context) ServeStream(rd io.Reader, name string) {
-	ctx.SetServeHeaders(name)
-	_, err := io.Copy(ctx.Resp, rd)
-	if err != nil {
-		ctx.ServerError("Download file failed", err)
+	if opts.Filename != "" {
+		disposition := opts.Disposition
+		if disposition == "" {
+			disposition = "attachment"
+		}
+
+		backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
+		header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
+		header.Set("Access-Control-Expose-Headers", "Content-Disposition")
+	}
+
+	duration := opts.CacheDuration
+	if duration == 0 {
+		duration = 5 * time.Minute
+	}
+	httpcache.AddCacheControlToHeader(header, duration)
+
+	if !opts.LastModified.IsZero() {
+		header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
 	}
 }
 
+// ServeContent serves content to http request
+func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
+	ctx.SetServeHeaders(opts)
+	http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
+}
+
 // UploadStream returns the request body or the first form file
 // Only form files need to get closed.
 func (ctx *Context) UploadStream() (rd io.ReadCloser, needToClose bool, err error) {
@@ -650,7 +663,13 @@ func getCsrfOpts() CsrfOptions {
 // Auth converts auth.Auth as a middleware
 func Auth(authMethod auth.Method) func(*Context) {
 	return func(ctx *Context) {
-		ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
+		var err error
+		ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
+		if err != nil {
+			log.Error("Failed to verify user %v: %v", ctx.Req.RemoteAddr, err)
+			ctx.Error(http.StatusUnauthorized, "Verify")
+			return
+		}
 		if ctx.Doer != nil {
 			if ctx.Locale.Language() != ctx.Doer.Language {
 				ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
@@ -673,8 +692,8 @@ func Auth(authMethod auth.Method) func(*Context) {
 }
 
 // Contexter initializes a classic context for a request.
-func Contexter() func(next http.Handler) http.Handler {
-	rnd := templates.HTMLRenderer()
+func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
+	_, rnd := templates.HTMLRenderer(ctx)
 	csrfOpts := getCsrfOpts()
 	if !setting.IsProd {
 		CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
@@ -767,6 +786,7 @@ func Contexter() func(next http.Handler) http.Handler {
 				}
 			}
 
+			httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
 			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 
 			ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
@@ -794,7 +814,7 @@ func Contexter() func(next http.Handler) http.Handler {
 			ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
 			ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
 
-			ctx.Data["i18n"] = locale
+			ctx.Data["locale"] = locale
 			ctx.Data["AllLangs"] = translation.AllLangs()
 
 			next.ServeHTTP(ctx.Resp, ctx.Req)
diff --git a/modules/context/org.go b/modules/context/org.go
index 9f4ce485e5ee7..89260b86544ec 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models/perm"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/structs"
 )
 
 // Organization contains organization context
@@ -69,6 +70,20 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 		return
 	}
 	org := ctx.Org.Organization
+
+	// Handle Visibility
+	if org.Visibility != structs.VisibleTypePublic && !ctx.IsSigned {
+		// We must be signed in to see limited or private organizations
+		ctx.NotFound("OrgAssignment", err)
+		return
+	}
+
+	if org.Visibility == structs.VisibleTypePrivate {
+		requireMember = true
+	} else if ctx.IsSigned && ctx.Doer.IsRestricted {
+		requireMember = true
+	}
+
 	ctx.ContextUser = org.AsUser()
 	ctx.Data["Org"] = org
 
@@ -115,6 +130,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 	ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
 	ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
 	ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
+	ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
 	ctx.Data["IsPublicMember"] = func(uid int64) bool {
 		is, _ := organization.IsPublicMembership(ctx.Org.Organization.ID, uid)
 		return is
diff --git a/modules/context/package.go b/modules/context/package.go
index 4c52907dc529c..ce0f9a511b382 100644
--- a/modules/context/package.go
+++ b/modules/context/package.go
@@ -5,14 +5,18 @@
 package context
 
 import (
+	gocontext "context"
 	"fmt"
 	"net/http"
 
 	"code.gitea.io/gitea/models/organization"
 	packages_model "code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/models/perm"
+	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 )
 
 // Package contains owner, access mode and optional the package descriptor
@@ -51,31 +55,11 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
 		Owner: ctx.ContextUser,
 	}
 
-	if ctx.Package.Owner.IsOrganization() {
-		// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
-		if ctx.Doer != nil {
-			var err error
-			ctx.Package.AccessMode, err = organization.OrgFromUser(ctx.Package.Owner).GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
-			if err != nil {
-				errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
-				return
-			}
-		}
-		// 2. If authorize level is none, check if org is visible to user
-		if ctx.Package.AccessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
-			ctx.Package.AccessMode = perm.AccessModeRead
-		}
-	} else {
-		if ctx.Doer != nil && !ctx.Doer.IsGhost() {
-			// 1. Check if user is package owner
-			if ctx.Doer.ID == ctx.Package.Owner.ID {
-				ctx.Package.AccessMode = perm.AccessModeOwner
-			} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
-				ctx.Package.AccessMode = perm.AccessModeRead
-			}
-		} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
-			ctx.Package.AccessMode = perm.AccessModeRead
-		}
+	var err error
+	ctx.Package.AccessMode, err = determineAccessMode(ctx)
+	if err != nil {
+		errCb(http.StatusInternalServerError, "determineAccessMode", err)
+		return
 	}
 
 	packageType := ctx.Params("type")
@@ -100,13 +84,69 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
 	}
 }
 
+func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
+	if setting.Service.RequireSignInView && ctx.Doer == nil {
+		return perm.AccessModeNone, nil
+	}
+
+	if ctx.Doer != nil && !ctx.Doer.IsGhost() && (!ctx.Doer.IsActive || ctx.Doer.ProhibitLogin) {
+		return perm.AccessModeNone, nil
+	}
+
+	accessMode := perm.AccessModeNone
+	if ctx.Package.Owner.IsOrganization() {
+		org := organization.OrgFromUser(ctx.Package.Owner)
+
+		// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
+		if ctx.Doer != nil {
+			var err error
+			accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
+			if err != nil {
+				return accessMode, err
+			}
+			// If access mode is less than write check every team for more permissions
+			if accessMode < perm.AccessModeWrite {
+				teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
+				if err != nil {
+					return accessMode, err
+				}
+				for _, t := range teams {
+					perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
+					if accessMode < perm {
+						accessMode = perm
+					}
+				}
+			}
+		}
+		// 2. If authorize level is none, check if org is visible to user
+		if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
+			accessMode = perm.AccessModeRead
+		}
+	} else {
+		if ctx.Doer != nil && !ctx.Doer.IsGhost() {
+			// 1. Check if user is package owner
+			if ctx.Doer.ID == ctx.Package.Owner.ID {
+				accessMode = perm.AccessModeOwner
+			} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
+				accessMode = perm.AccessModeRead
+			}
+		} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
+			accessMode = perm.AccessModeRead
+		}
+	}
+
+	return accessMode, nil
+}
+
 // PackageContexter initializes a package context for a request.
-func PackageContexter() func(next http.Handler) http.Handler {
+func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler {
+	_, rnd := templates.HTMLRenderer(ctx)
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 			ctx := Context{
-				Resp: NewResponse(resp),
-				Data: map[string]interface{}{},
+				Resp:   NewResponse(resp),
+				Data:   map[string]interface{}{},
+				Render: rnd,
 			}
 			defer ctx.Close()
 
diff --git a/modules/context/private.go b/modules/context/private.go
index fdc7751227ea7..9e7977b5d5542 100644
--- a/modules/context/private.go
+++ b/modules/context/private.go
@@ -82,5 +82,5 @@ func PrivateContexter() func(http.Handler) http.Handler {
 func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) {
 	// We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
 	ctx.Override, _, cancel = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
-	return
+	return cancel
 }
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 8e75ad07d58f2..3e2f794303dd3 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -9,7 +9,6 @@ import (
 	"context"
 	"fmt"
 	"html"
-	"io"
 	"net/http"
 	"net/url"
 	"path"
@@ -26,8 +25,8 @@ import (
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
 	code_indexer "code.gitea.io/gitea/modules/indexer/code"
+	"code.gitea.io/gitea/modules/issue/template"
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/markup/markdown"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
@@ -118,7 +117,8 @@ type CanCommitToBranchResults struct {
 }
 
 // CanCommitToBranch returns true if repository is editable and user has proper access level
-//   and branch is not protected for push
+//
+// and branch is not protected for push
 func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
 	protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
 	if err != nil {
@@ -393,7 +393,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
 		}
 	}
 
-	pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID)
+	pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
 	if err != nil {
 		ctx.ServerError("GetPushMirrorsByRepoID", err)
 		return
@@ -451,11 +451,20 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 		owner, err = user_model.GetUserByName(ctx, userName)
 		if err != nil {
 			if user_model.IsErrUserNotExist(err) {
+				// go-get does not support redirects
+				// https://github.com/golang/go/issues/19760
 				if ctx.FormString("go-get") == "1" {
 					EarlyResponseForGoGetMeta(ctx)
 					return
 				}
-				ctx.NotFound("GetUserByName", nil)
+
+				if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
+					RedirectToUser(ctx, userName, redirectUserID)
+				} else if user_model.IsErrUserRedirectNotExist(err) {
+					ctx.NotFound("GetUserByName", nil)
+				} else {
+					ctx.ServerError("LookupUserRedirect", err)
+				}
 			} else {
 				ctx.ServerError("GetUserByName", err)
 			}
@@ -523,14 +532,16 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 		ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
 	}
 
-	ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
-		IncludeTags: true,
+	ctx.Data["NumTags"], err = repo_model.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
+		IncludeDrafts: true,
+		IncludeTags:   true,
+		HasSha1:       util.OptionalBoolTrue, // only draft releases which are created with existing tags
 	})
 	if err != nil {
 		ctx.ServerError("GetReleaseCountByRepoID", err)
 		return
 	}
-	ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
+	ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{})
 	if err != nil {
 		ctx.ServerError("GetReleaseCountByRepoID", err)
 		return
@@ -734,7 +745,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
 		ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
 		ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
 	}
-	return
+	return cancel
 }
 
 // RepoRefType type of repo reference
@@ -805,7 +816,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
 		}
 		// For legacy and API support only full commit sha
 		parts := strings.Split(path, "/")
-		if len(parts) > 0 && len(parts[0]) == 40 {
+		if len(parts) > 0 && len(parts[0]) == git.SHAFullLength {
 			ctx.Repo.TreePath = strings.Join(parts[1:], "/")
 			return parts[0]
 		}
@@ -841,7 +852,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
 		return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
 	case RepoRefCommit:
 		parts := strings.Split(path, "/")
-		if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 {
+		if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength {
 			ctx.Repo.TreePath = strings.Join(parts[1:], "/")
 			return parts[0]
 		}
@@ -942,11 +953,15 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 
 				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
 				if err != nil {
+					if git.IsErrNotExist(err) {
+						ctx.NotFound("GetTagCommit", err)
+						return
+					}
 					ctx.ServerError("GetTagCommit", err)
 					return
 				}
 				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
-			} else if len(refName) >= 7 && len(refName) <= 40 {
+			} else if len(refName) >= 7 && len(refName) <= git.SHAFullLength {
 				ctx.Repo.IsViewCommit = true
 				ctx.Repo.CommitID = refName
 
@@ -956,7 +971,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 					return
 				}
 				// If short commit ID add canonical link header
-				if len(refName) < 40 {
+				if len(refName) < git.SHAFullLength {
 					ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
 						util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
 				}
@@ -982,6 +997,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 		}
 
 		ctx.Data["BranchName"] = ctx.Repo.BranchName
+		ctx.Data["RefName"] = ctx.Repo.RefName
 		ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
 		ctx.Data["TagName"] = ctx.Repo.TagName
 		ctx.Data["CommitID"] = ctx.Repo.CommitID
@@ -997,7 +1013,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
 			return
 		}
 		ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
-		return
+		ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
+
+		return cancel
 	}
 }
 
@@ -1026,70 +1044,52 @@ func UnitTypes() func(ctx *Context) {
 	}
 }
 
-// IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch
-func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate {
-	var issueTemplates []api.IssueTemplate
+// IssueTemplatesFromDefaultBranch checks for valid issue templates in the repo's default branch,
+func (ctx *Context) IssueTemplatesFromDefaultBranch() []*api.IssueTemplate {
+	ret, _ := ctx.IssueTemplatesErrorsFromDefaultBranch()
+	return ret
+}
+
+// IssueTemplatesErrorsFromDefaultBranch checks for issue templates in the repo's default branch,
+// returns valid templates and the errors of invalid template files.
+func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplate, map[string]error) {
+	var issueTemplates []*api.IssueTemplate
 
 	if ctx.Repo.Repository.IsEmpty {
-		return issueTemplates
+		return issueTemplates, nil
 	}
 
 	if ctx.Repo.Commit == nil {
 		var err error
 		ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
 		if err != nil {
-			return issueTemplates
+			return issueTemplates, nil
 		}
 	}
 
+	invalidFiles := map[string]error{}
 	for _, dirName := range IssueTemplateDirCandidates {
 		tree, err := ctx.Repo.Commit.SubTree(dirName)
 		if err != nil {
+			log.Debug("get sub tree of %s: %v", dirName, err)
 			continue
 		}
 		entries, err := tree.ListEntries()
 		if err != nil {
-			return issueTemplates
+			log.Debug("list entries in %s: %v", dirName, err)
+			return issueTemplates, nil
 		}
 		for _, entry := range entries {
-			if strings.HasSuffix(entry.Name(), ".md") {
-				if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
-					log.Debug("Issue template is too large: %s", entry.Name())
-					continue
-				}
-				r, err := entry.Blob().DataAsync()
-				if err != nil {
-					log.Debug("DataAsync: %v", err)
-					continue
-				}
-				closed := false
-				defer func() {
-					if !closed {
-						_ = r.Close()
-					}
-				}()
-				data, err := io.ReadAll(r)
-				if err != nil {
-					log.Debug("ReadAll: %v", err)
-					continue
-				}
-				_ = r.Close()
-				var it api.IssueTemplate
-				content, err := markdown.ExtractMetadata(string(data), &it)
-				if err != nil {
-					log.Debug("ExtractMetadata: %v", err)
-					continue
-				}
-				it.Content = content
-				it.FileName = entry.Name()
-				if it.Valid() {
-					issueTemplates = append(issueTemplates, it)
-				}
+			if !template.CouldBe(entry.Name()) {
+				continue
+			}
+			fullName := path.Join(dirName, entry.Name())
+			if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
+				invalidFiles[fullName] = err
+			} else {
+				issueTemplates = append(issueTemplates, it)
 			}
-		}
-		if len(issueTemplates) > 0 {
-			return issueTemplates
 		}
 	}
-	return issueTemplates
+	return issueTemplates, invalidFiles
 }
diff --git a/modules/context/utils.go b/modules/context/utils.go
index aea51cc5d6708..a72c8b47e6530 100644
--- a/modules/context/utils.go
+++ b/modules/context/utils.go
@@ -52,5 +52,5 @@ func parseTime(value string) (int64, error) {
 func prepareQueryArg(ctx *Context, name string) (value string, err error) {
 	value, err = url.PathUnescape(ctx.FormString(name))
 	value = strings.TrimSpace(value)
-	return
+	return value, err
 }
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index c8cb23261efdd..8c92bbb3712f2 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -27,6 +27,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/services/gitdiff"
 	webhook_service "code.gitea.io/gitea/services/webhook"
 )
 
@@ -257,7 +258,7 @@ func ToHook(repoLink string, w *webhook.Webhook) *api.Hook {
 
 	return &api.Hook{
 		ID:      w.ID,
-		Type:    string(w.Type),
+		Type:    w.Type,
 		URL:     fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID),
 		Active:  w.IsActive,
 		Config:  config,
@@ -295,6 +296,7 @@ func ToOrganization(org *organization.Organization) *api.Organization {
 	return &api.Organization{
 		ID:                        org.ID,
 		AvatarURL:                 org.AsUser().AvatarLink(),
+		Name:                      org.Name,
 		UserName:                  org.Name,
 		FullName:                  org.FullName,
 		Description:               org.Description,
@@ -390,12 +392,13 @@ func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
 // ToOAuth2Application convert from auth.OAuth2Application to api.OAuth2Application
 func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
 	return &api.OAuth2Application{
-		ID:           app.ID,
-		Name:         app.Name,
-		ClientID:     app.ClientID,
-		ClientSecret: app.ClientSecret,
-		RedirectURIs: app.RedirectURIs,
-		Created:      app.CreatedUnix.AsTime(),
+		ID:                 app.ID,
+		Name:               app.Name,
+		ClientID:           app.ClientID,
+		ClientSecret:       app.ClientSecret,
+		ConfidentialClient: app.ConfidentialClient,
+		RedirectURIs:       app.RedirectURIs,
+		Created:            app.CreatedUnix.AsTime(),
 	}
 }
 
@@ -410,7 +413,40 @@ func ToLFSLock(l *git_model.LFSLock) *api.LFSLock {
 		Path:     l.Path,
 		LockedAt: l.Created.Round(time.Second),
 		Owner: &api.LFSLockOwner{
-			Name: u.DisplayName(),
+			Name: u.Name,
 		},
 	}
 }
+
+// ToChangedFile convert a gitdiff.DiffFile to api.ChangedFile
+func ToChangedFile(f *gitdiff.DiffFile, repo *repo_model.Repository, commit string) *api.ChangedFile {
+	status := "changed"
+	if f.IsDeleted {
+		status = "deleted"
+	} else if f.IsCreated {
+		status = "added"
+	} else if f.IsRenamed && f.Type == gitdiff.DiffFileCopy {
+		status = "copied"
+	} else if f.IsRenamed && f.Type == gitdiff.DiffFileRename {
+		status = "renamed"
+	} else if f.Addition == 0 && f.Deletion == 0 {
+		status = "unchanged"
+	}
+
+	file := &api.ChangedFile{
+		Filename:    f.GetDiffFileName(),
+		Status:      status,
+		Additions:   f.Addition,
+		Deletions:   f.Deletion,
+		Changes:     f.Addition + f.Deletion,
+		HTMLURL:     fmt.Sprint(repo.HTMLURL(), "/src/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
+		ContentsURL: fmt.Sprint(repo.APIURL(), "/contents/", util.PathEscapeSegments(f.GetDiffFileName()), "?ref=", commit),
+		RawURL:      fmt.Sprint(repo.HTMLURL(), "/raw/commit/", commit, "/", util.PathEscapeSegments(f.GetDiffFileName())),
+	}
+
+	if status == "rename" {
+		file.PreviousFilename = f.OldName
+	}
+
+	return file
+}
diff --git a/modules/convert/git_commit.go b/modules/convert/git_commit.go
index dfd6cb080ca35..6015a73712896 100644
--- a/modules/convert/git_commit.go
+++ b/modules/convert/git_commit.go
@@ -73,7 +73,7 @@ func ToPayloadCommit(repo *repo_model.Repository, c *git.Commit) *api.PayloadCom
 }
 
 // ToCommit convert a git.Commit to api.Commit
-func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User) (*api.Commit, error) {
+func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, stat bool) (*api.Commit, error) {
 	var apiAuthor, apiCommitter *api.User
 
 	// Retrieve author and committer information
@@ -133,28 +133,7 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
 		}
 	}
 
-	// Retrieve files affected by the commit
-	fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
-	if err != nil {
-		return nil, err
-	}
-	affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
-	for _, files := range [][]string{fileStatus.Added, fileStatus.Removed, fileStatus.Modified} {
-		for _, filename := range files {
-			affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
-				Filename: filename,
-			})
-		}
-	}
-
-	diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
-		AfterCommitID: commit.ID.String(),
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return &api.Commit{
+	res := &api.Commit{
 		CommitMeta: &api.CommitMeta{
 			URL:     repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
 			SHA:     commit.ID.String(),
@@ -188,11 +167,37 @@ func ToCommit(repo *repo_model.Repository, gitRepo *git.Repository, commit *git.
 		Author:    apiAuthor,
 		Committer: apiCommitter,
 		Parents:   apiParents,
-		Files:     affectedFileList,
-		Stats: &api.CommitStats{
+	}
+
+	// Retrieve files affected by the commit
+	if stat {
+		fileStatus, err := git.GetCommitFileStatus(gitRepo.Ctx, repo.RepoPath(), commit.ID.String())
+		if err != nil {
+			return nil, err
+		}
+		affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
+		for _, files := range [][]string{fileStatus.Added, fileStatus.Removed, fileStatus.Modified} {
+			for _, filename := range files {
+				affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
+					Filename: filename,
+				})
+			}
+		}
+
+		diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
+			AfterCommitID: commit.ID.String(),
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		res.Files = affectedFileList
+		res.Stats = &api.CommitStats{
 			Total:     diff.TotalAddition + diff.TotalDeletion,
 			Additions: diff.TotalAddition,
 			Deletions: diff.TotalDeletion,
-		},
-	}, nil
+		}
+	}
+
+	return res, nil
 }
diff --git a/modules/convert/git_commit_test.go b/modules/convert/git_commit_test.go
index 118ba3a007a2d..0bba0e502e6d1 100644
--- a/modules/convert/git_commit_test.go
+++ b/modules/convert/git_commit_test.go
@@ -19,7 +19,7 @@ import (
 
 func TestToCommitMeta(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
+	headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 	sha1, _ := git.NewIDFromString("0000000000000000000000000000000000000000")
 	signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)}
 	tag := &git.Tag{
diff --git a/modules/convert/issue.go b/modules/convert/issue.go
index 35eff052291ab..feedc6f8de8c5 100644
--- a/modules/convert/issue.go
+++ b/modules/convert/issue.go
@@ -110,12 +110,11 @@ func ToAPIIssueList(il issues_model.IssueList) []*api.Issue {
 // ToTrackedTime converts TrackedTime to API format
 func ToTrackedTime(t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
 	apiT = &api.TrackedTime{
-		ID:       t.ID,
-		IssueID:  t.IssueID,
-		UserID:   t.UserID,
-		UserName: t.User.Name,
-		Time:     t.Time,
-		Created:  t.Created,
+		ID:      t.ID,
+		IssueID: t.IssueID,
+		UserID:  t.UserID,
+		Time:    t.Time,
+		Created: t.Created,
 	}
 	if t.Issue != nil {
 		apiT.Issue = ToAPIIssue(t.Issue)
@@ -123,7 +122,7 @@ func ToTrackedTime(t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
 	if t.User != nil {
 		apiT.UserName = t.User.Name
 	}
-	return
+	return apiT
 }
 
 // ToStopWatches convert Stopwatch list to api.StopWatches
diff --git a/modules/convert/issue_comment.go b/modules/convert/issue_comment.go
index ccc94b249636f..73ad345fa43f9 100644
--- a/modules/convert/issue_comment.go
+++ b/modules/convert/issue_comment.go
@@ -101,6 +101,12 @@ func ToTimelineComment(c *issues_model.Comment, doer *user_model.User) *api.Time
 	}
 
 	if c.Time != nil {
+		err = c.Time.LoadAttributes()
+		if err != nil {
+			log.Error("Time.LoadAttributes: %v", err)
+			return nil
+		}
+
 		comment.TrackedTime = ToTrackedTime(c.Time)
 	}
 
diff --git a/modules/convert/issue_test.go b/modules/convert/issue_test.go
index 5bf04bcb52b6d..ec672abad2777 100644
--- a/modules/convert/issue_test.go
+++ b/modules/convert/issue_test.go
@@ -21,8 +21,8 @@ import (
 
 func TestLabel_ToLabel(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}).(*issues_model.Label)
-	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: label.RepoID}).(*repo_model.Repository)
+	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: label.RepoID})
 	assert.Equal(t, &api.Label{
 		ID:    label.ID,
 		Name:  label.Name,
diff --git a/modules/convert/mirror.go b/modules/convert/mirror.go
new file mode 100644
index 0000000000000..b2414f46774cc
--- /dev/null
+++ b/modules/convert/mirror.go
@@ -0,0 +1,39 @@
+// 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 convert
+
+import (
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/git"
+	api "code.gitea.io/gitea/modules/structs"
+)
+
+// ToPushMirror convert from repo_model.PushMirror and remoteAddress to api.TopicResponse
+func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
+	repo := pm.GetRepository()
+	remoteAddress, err := getRemoteAddress(repo, pm.RemoteName)
+	if err != nil {
+		return nil, err
+	}
+	return &api.PushMirror{
+		RepoName:       repo.Name,
+		RemoteName:     pm.RemoteName,
+		RemoteAddress:  remoteAddress,
+		CreatedUnix:    pm.CreatedUnix.FormatLong(),
+		LastUpdateUnix: pm.LastUpdateUnix.FormatLong(),
+		LastError:      pm.LastError,
+		Interval:       pm.Interval.String(),
+	}, nil
+}
+
+func getRemoteAddress(repo *repo_model.Repository, remoteName string) (string, error) {
+	url, err := git.GetRemoteURL(git.DefaultContext, repo.RepoPath(), remoteName)
+	if err != nil {
+		return "", err
+	}
+	// remove confidential information
+	url.User = nil
+	return url.String(), nil
+}
diff --git a/modules/convert/notification.go b/modules/convert/notification.go
index 1efba5745ccda..55f782f8f6730 100644
--- a/modules/convert/notification.go
+++ b/modules/convert/notification.go
@@ -7,17 +7,17 @@ package convert
 import (
 	"net/url"
 
-	"code.gitea.io/gitea/models"
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/perm"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
 // ToNotificationThread convert a Notification to api.NotificationThread
-func ToNotificationThread(n *models.Notification) *api.NotificationThread {
+func ToNotificationThread(n *activities_model.Notification) *api.NotificationThread {
 	result := &api.NotificationThread{
 		ID:        n.ID,
-		Unread:    !(n.Status == models.NotificationStatusRead || n.Status == models.NotificationStatusPinned),
-		Pinned:    n.Status == models.NotificationStatusPinned,
+		Unread:    !(n.Status == activities_model.NotificationStatusRead || n.Status == activities_model.NotificationStatusPinned),
+		Pinned:    n.Status == activities_model.NotificationStatusPinned,
 		UpdatedAt: n.UpdatedUnix.AsTime(),
 		URL:       n.APIURL(),
 	}
@@ -34,7 +34,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 
 	// handle Subject
 	switch n.Source {
-	case models.NotificationSourceIssue:
+	case activities_model.NotificationSourceIssue:
 		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectIssue}
 		if n.Issue != nil {
 			result.Subject.Title = n.Issue.Title
@@ -47,7 +47,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 				result.Subject.LatestCommentHTMLURL = comment.HTMLURL()
 			}
 		}
-	case models.NotificationSourcePullRequest:
+	case activities_model.NotificationSourcePullRequest:
 		result.Subject = &api.NotificationSubject{Type: api.NotifySubjectPull}
 		if n.Issue != nil {
 			result.Subject.Title = n.Issue.Title
@@ -65,7 +65,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 				result.Subject.State = "merged"
 			}
 		}
-	case models.NotificationSourceCommit:
+	case activities_model.NotificationSourceCommit:
 		url := n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
 		result.Subject = &api.NotificationSubject{
 			Type:    api.NotifySubjectCommit,
@@ -73,7 +73,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 			URL:     url,
 			HTMLURL: url,
 		}
-	case models.NotificationSourceRepository:
+	case activities_model.NotificationSourceRepository:
 		result.Subject = &api.NotificationSubject{
 			Type:  api.NotifySubjectRepository,
 			Title: n.Repository.FullName(),
@@ -87,7 +87,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
 }
 
 // ToNotifications convert list of Notification to api.NotificationThread list
-func ToNotifications(nl models.NotificationList) []*api.NotificationThread {
+func ToNotifications(nl activities_model.NotificationList) []*api.NotificationThread {
 	result := make([]*api.NotificationThread, 0, len(nl))
 	for _, n := range nl {
 		result = append(result, ToNotificationThread(n))
diff --git a/modules/convert/pull_test.go b/modules/convert/pull_test.go
index 10ef311399a6d..a6ccbaca5897d 100644
--- a/modules/convert/pull_test.go
+++ b/modules/convert/pull_test.go
@@ -20,8 +20,8 @@ import (
 func TestPullRequest_APIFormat(t *testing.T) {
 	// with HeadRepo
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadAttributes())
 	assert.NoError(t, pr.LoadIssue())
 	apiPullRequest := ToAPIPullRequest(git.DefaultContext, pr, nil)
@@ -35,7 +35,7 @@ func TestPullRequest_APIFormat(t *testing.T) {
 	}, apiPullRequest.Head)
 
 	// withOut HeadRepo
-	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}).(*issues_model.PullRequest)
+	pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
 	assert.NoError(t, pr.LoadIssue())
 	assert.NoError(t, pr.LoadAttributes())
 	// simulate fork deletion
diff --git a/modules/convert/release.go b/modules/convert/release.go
index 955d3ff05fb98..5fc95dab72b7e 100644
--- a/modules/convert/release.go
+++ b/modules/convert/release.go
@@ -5,13 +5,12 @@
 package convert
 
 import (
-	"code.gitea.io/gitea/models"
 	repo_model "code.gitea.io/gitea/models/repo"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
-// ToRelease convert a models.Release to api.Release
-func ToRelease(r *models.Release) *api.Release {
+// ToRelease convert a repo_model.Release to api.Release
+func ToRelease(r *repo_model.Release) *api.Release {
 	assets := make([]*api.Attachment, 0)
 	for _, att := range r.Attachments {
 		assets = append(assets, ToReleaseAttachment(att))
diff --git a/modules/convert/repository.go b/modules/convert/repository.go
index eb6bb37707391..09b84afa6c4c6 100644
--- a/modules/convert/repository.go
+++ b/modules/convert/repository.go
@@ -56,9 +56,10 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 		config := unit.ExternalTrackerConfig()
 		hasIssues = true
 		externalTracker = &api.ExternalTracker{
-			ExternalTrackerURL:    config.ExternalTrackerURL,
-			ExternalTrackerFormat: config.ExternalTrackerFormat,
-			ExternalTrackerStyle:  config.ExternalTrackerStyle,
+			ExternalTrackerURL:           config.ExternalTrackerURL,
+			ExternalTrackerFormat:        config.ExternalTrackerFormat,
+			ExternalTrackerStyle:         config.ExternalTrackerStyle,
+			ExternalTrackerRegexpPattern: config.ExternalTrackerRegexpPattern,
 		}
 	}
 	hasWiki := false
@@ -78,6 +79,8 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 	allowRebase := false
 	allowRebaseMerge := false
 	allowSquash := false
+	allowRebaseUpdate := false
+	defaultDeleteBranchAfterMerge := false
 	defaultMergeStyle := repo_model.MergeStyleMerge
 	if unit, err := repo.GetUnit(unit_model.TypePullRequests); err == nil {
 		config := unit.PullRequestsConfig()
@@ -87,6 +90,8 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 		allowRebase = config.AllowRebase
 		allowRebaseMerge = config.AllowRebaseMerge
 		allowSquash = config.AllowSquash
+		allowRebaseUpdate = config.AllowRebaseUpdate
+		defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge
 		defaultMergeStyle = config.GetDefaultMergeStyle()
 	}
 	hasProjects := false
@@ -98,7 +103,7 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 		return nil
 	}
 
-	numReleases, _ := models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false})
+	numReleases, _ := repo_model.GetReleaseCountByRepoID(repo.ID, repo_model.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false})
 
 	mirrorInterval := ""
 	var mirrorUpdated time.Time
@@ -133,54 +138,56 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
 	repoAPIURL := repo.APIURL()
 
 	return &api.Repository{
-		ID:                        repo.ID,
-		Owner:                     ToUserWithAccessMode(repo.Owner, mode),
-		Name:                      repo.Name,
-		FullName:                  repo.FullName(),
-		Description:               repo.Description,
-		Private:                   repo.IsPrivate,
-		Template:                  repo.IsTemplate,
-		Empty:                     repo.IsEmpty,
-		Archived:                  repo.IsArchived,
-		Size:                      int(repo.Size / 1024),
-		Fork:                      repo.IsFork,
-		Parent:                    parent,
-		Mirror:                    repo.IsMirror,
-		HTMLURL:                   repo.HTMLURL(),
-		SSHURL:                    cloneLink.SSH,
-		CloneURL:                  cloneLink.HTTPS,
-		OriginalURL:               repo.SanitizedOriginalURL(),
-		Website:                   repo.Website,
-		Language:                  language,
-		LanguagesURL:              repoAPIURL + "/languages",
-		Stars:                     repo.NumStars,
-		Forks:                     repo.NumForks,
-		Watchers:                  repo.NumWatches,
-		OpenIssues:                repo.NumOpenIssues,
-		OpenPulls:                 repo.NumOpenPulls,
-		Releases:                  int(numReleases),
-		DefaultBranch:             repo.DefaultBranch,
-		Created:                   repo.CreatedUnix.AsTime(),
-		Updated:                   repo.UpdatedUnix.AsTime(),
-		Permissions:               permission,
-		HasIssues:                 hasIssues,
-		ExternalTracker:           externalTracker,
-		InternalTracker:           internalTracker,
-		HasWiki:                   hasWiki,
-		HasProjects:               hasProjects,
-		ExternalWiki:              externalWiki,
-		HasPullRequests:           hasPullRequests,
-		IgnoreWhitespaceConflicts: ignoreWhitespaceConflicts,
-		AllowMerge:                allowMerge,
-		AllowRebase:               allowRebase,
-		AllowRebaseMerge:          allowRebaseMerge,
-		AllowSquash:               allowSquash,
-		DefaultMergeStyle:         string(defaultMergeStyle),
-		AvatarURL:                 repo.AvatarLink(),
-		Internal:                  !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
-		MirrorInterval:            mirrorInterval,
-		MirrorUpdated:             mirrorUpdated,
-		RepoTransfer:              transfer,
+		ID:                            repo.ID,
+		Owner:                         ToUserWithAccessMode(repo.Owner, mode),
+		Name:                          repo.Name,
+		FullName:                      repo.FullName(),
+		Description:                   repo.Description,
+		Private:                       repo.IsPrivate,
+		Template:                      repo.IsTemplate,
+		Empty:                         repo.IsEmpty,
+		Archived:                      repo.IsArchived,
+		Size:                          int(repo.Size / 1024),
+		Fork:                          repo.IsFork,
+		Parent:                        parent,
+		Mirror:                        repo.IsMirror,
+		HTMLURL:                       repo.HTMLURL(),
+		SSHURL:                        cloneLink.SSH,
+		CloneURL:                      cloneLink.HTTPS,
+		OriginalURL:                   repo.SanitizedOriginalURL(),
+		Website:                       repo.Website,
+		Language:                      language,
+		LanguagesURL:                  repoAPIURL + "/languages",
+		Stars:                         repo.NumStars,
+		Forks:                         repo.NumForks,
+		Watchers:                      repo.NumWatches,
+		OpenIssues:                    repo.NumOpenIssues,
+		OpenPulls:                     repo.NumOpenPulls,
+		Releases:                      int(numReleases),
+		DefaultBranch:                 repo.DefaultBranch,
+		Created:                       repo.CreatedUnix.AsTime(),
+		Updated:                       repo.UpdatedUnix.AsTime(),
+		Permissions:                   permission,
+		HasIssues:                     hasIssues,
+		ExternalTracker:               externalTracker,
+		InternalTracker:               internalTracker,
+		HasWiki:                       hasWiki,
+		HasProjects:                   hasProjects,
+		ExternalWiki:                  externalWiki,
+		HasPullRequests:               hasPullRequests,
+		IgnoreWhitespaceConflicts:     ignoreWhitespaceConflicts,
+		AllowMerge:                    allowMerge,
+		AllowRebase:                   allowRebase,
+		AllowRebaseMerge:              allowRebaseMerge,
+		AllowSquash:                   allowSquash,
+		AllowRebaseUpdate:             allowRebaseUpdate,
+		DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge,
+		DefaultMergeStyle:             string(defaultMergeStyle),
+		AvatarURL:                     repo.AvatarLink(),
+		Internal:                      !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
+		MirrorInterval:                mirrorInterval,
+		MirrorUpdated:                 mirrorUpdated,
+		RepoTransfer:                  transfer,
 	}
 }
 
diff --git a/modules/convert/user.go b/modules/convert/user.go
index 2b07d21838d71..093994856cae7 100644
--- a/modules/convert/user.go
+++ b/modules/convert/user.go
@@ -73,6 +73,7 @@ func toUser(user *user_model.User, signed, authed bool) *api.User {
 	// only site admin will get these information and possibly user himself
 	if authed {
 		result.IsAdmin = user.IsAdmin
+		result.LoginName = user.LoginName
 		result.LastLogin = user.LastLoginUnix.AsTime()
 		result.Language = user.Language
 		result.IsActive = user.IsActive
diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go
index 2ed962950fffa..89d912e460c54 100644
--- a/modules/convert/user_test.go
+++ b/modules/convert/user_test.go
@@ -17,13 +17,13 @@ import (
 func TestUser_ToUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1, IsAdmin: true}).(*user_model.User)
+	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1, IsAdmin: true})
 
 	apiUser := toUser(user1, true, true)
 	assert.True(t, apiUser.IsAdmin)
 	assert.Contains(t, apiUser.AvatarURL, "://")
 
-	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2, IsAdmin: false}).(*user_model.User)
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2, IsAdmin: false})
 
 	apiUser = toUser(user2, true, true)
 	assert.False(t, apiUser.IsAdmin)
@@ -32,7 +32,7 @@ func TestUser_ToUser(t *testing.T) {
 	assert.False(t, apiUser.IsAdmin)
 	assert.EqualValues(t, api.VisibleTypePublic.String(), apiUser.Visibility)
 
-	user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}).(*user_model.User)
+	user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate})
 
 	apiUser = toUser(user31, true, true)
 	assert.False(t, apiUser.IsAdmin)
diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go
index 9d0848ae5b119..5a8e13c811f7e 100644
--- a/modules/csv/csv_test.go
+++ b/modules/csv/csv_test.go
@@ -322,7 +322,7 @@ func TestGuessDelimiter(t *testing.T) {
 		},
 		// case 3 - tab delimited
 		{
-			csv: "1	2",
+			csv:               "1\t2",
 			expectedDelimiter: '\t',
 		},
 		// case 4 - pipe delimited
diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go
index 34dfe939d3e48..b3e9699a020d1 100644
--- a/modules/doctor/authorizedkeys.go
+++ b/modules/doctor/authorizedkeys.go
@@ -14,6 +14,7 @@ import (
 	"strings"
 
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -30,17 +31,17 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 	if err != nil {
 		if !autofix {
 			logger.Critical("Unable to open authorized_keys file. ERROR: %v", err)
-			return fmt.Errorf("Unable to open authorized_keys file. ERROR: %v", err)
+			return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err)
 		}
 		logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err)
 		if err = asymkey_model.RewriteAllPublicKeys(); err != nil {
 			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
-			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %v", err)
+			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
 		}
 	}
 	defer f.Close()
 
-	linesInAuthorizedKeys := map[string]bool{}
+	linesInAuthorizedKeys := make(container.Set[string])
 
 	scanner := bufio.NewScanner(f)
 	for scanner.Scan() {
@@ -48,7 +49,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 		if strings.HasPrefix(line, tplCommentPrefix) {
 			continue
 		}
-		linesInAuthorizedKeys[line] = true
+		linesInAuthorizedKeys.Add(line)
 	}
 	f.Close()
 
@@ -56,7 +57,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 	regenerated := &bytes.Buffer{}
 	if err := asymkey_model.RegeneratePublicKeys(ctx, regenerated); err != nil {
 		logger.Critical("Unable to regenerate authorized_keys file. ERROR: %v", err)
-		return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %v", err)
+		return fmt.Errorf("Unable to regenerate authorized_keys file. ERROR: %w", err)
 	}
 	scanner = bufio.NewScanner(regenerated)
 	for scanner.Scan() {
@@ -64,7 +65,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 		if strings.HasPrefix(line, tplCommentPrefix) {
 			continue
 		}
-		if ok := linesInAuthorizedKeys[line]; ok {
+		if linesInAuthorizedKeys.Contains(line) {
 			continue
 		}
 		if !autofix {
@@ -79,7 +80,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
 		err = asymkey_model.RewriteAllPublicKeys()
 		if err != nil {
 			logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err)
-			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %v", err)
+			return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err)
 		}
 	}
 	return nil
diff --git a/modules/doctor/breaking.go b/modules/doctor/breaking.go
index c4b58d20fb26d..51122d9a61a5c 100644
--- a/modules/doctor/breaking.go
+++ b/modules/doctor/breaking.go
@@ -32,8 +32,8 @@ func iterateUserAccounts(ctx context.Context, each func(*user.User) error) error
 // Ref: https://github.com/go-gitea/gitea/pull/19085 & https://github.com/go-gitea/gitea/pull/17688
 func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
 	// We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean
-	// DB provider-specific SQL and only works _now_. So instead we iterate trough all user accounts and
-	// use the user.ValidateEmail function to be future-proof.
+	// DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts
+	// and use the user.ValidateEmail function to be future-proof.
 	var invalidUserCount int64
 	if err := iterateUserAccounts(ctx, func(u *user.User) error {
 		// Only check for users, skip
@@ -47,7 +47,7 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
 		}
 		return nil
 	}); err != nil {
-		return fmt.Errorf("iterateUserAccounts: %v", err)
+		return fmt.Errorf("iterateUserAccounts: %w", err)
 	}
 
 	if invalidUserCount == 0 {
@@ -58,6 +58,29 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
 	return nil
 }
 
+// From time to time Gitea makes changes to the reserved usernames and which symbols
+// are allowed for various reasons. This check helps with detecting users that, according
+// to our reserved names, don't have a valid username.
+func checkUserName(ctx context.Context, logger log.Logger, _ bool) error {
+	var invalidUserCount int64
+	if err := iterateUserAccounts(ctx, func(u *user.User) error {
+		if err := user.IsUsableUsername(u.Name); err != nil {
+			invalidUserCount++
+			logger.Warn("User[id=%d] does not have a valid username: %v", u.ID, err)
+		}
+		return nil
+	}); err != nil {
+		return fmt.Errorf("iterateUserAccounts: %w", err)
+	}
+
+	if invalidUserCount == 0 {
+		logger.Info("All users have a valid username.")
+	} else {
+		logger.Warn("%d user(s) have a non-valid username.", invalidUserCount)
+	}
+	return nil
+}
+
 func init() {
 	Register(&Check{
 		Title:     "Check if users has an valid email address",
@@ -66,4 +89,11 @@ func init() {
 		Run:       checkUserEmail,
 		Priority:  9,
 	})
+	Register(&Check{
+		Title:     "Check if users have a valid username",
+		Name:      "check-user-names",
+		IsDefault: false,
+		Run:       checkUserName,
+		Priority:  9,
+	})
 }
diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go
index 349a2121cf572..89d974a35002d 100644
--- a/modules/doctor/dbconsistency.go
+++ b/modules/doctor/dbconsistency.go
@@ -7,7 +7,7 @@ package doctor
 import (
 	"context"
 
-	"code.gitea.io/gitea/models"
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/migrations"
@@ -121,8 +121,8 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
 		// find null archived repositories
 		{
 			Name:         "Repositories with is_archived IS NULL",
-			Counter:      models.CountNullArchivedRepository,
-			Fixer:        models.FixNullArchivedRepository,
+			Counter:      repo_model.CountNullArchivedRepository,
+			Fixer:        repo_model.FixNullArchivedRepository,
 			FixedMessage: "Fixed",
 		},
 		// find label comments with empty labels
@@ -148,8 +148,8 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
 		},
 		{
 			Name:         "Action with created_unix set as an empty string",
-			Counter:      models.CountActionCreatedUnixString,
-			Fixer:        models.FixActionCreatedUnixString,
+			Counter:      activities_model.CountActionCreatedUnixString,
+			Fixer:        activities_model.FixActionCreatedUnixString,
 			FixedMessage: "Set to zero",
 		},
 	}
@@ -201,10 +201,13 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
 			"oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),
 		// find stopwatches without existing user
 		genericOrphanCheck("Orphaned Stopwatches without existing User",
-			"stopwatch", "user", "stopwatch.user_id=user.id"),
+			"stopwatch", "user", "stopwatch.user_id=`user`.id"),
 		// find stopwatches without existing issue
 		genericOrphanCheck("Orphaned Stopwatches without existing Issue",
-			"stopwatch", "issue", "stopwatch.issue_id=issue.id"),
+			"stopwatch", "issue", "stopwatch.issue_id=`issue`.id"),
+		// find redirects without existing user.
+		genericOrphanCheck("Orphaned Redirects without existing redirect user",
+			"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
 	)
 
 	for _, c := range consistencyChecks {
diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go
index c8975a788e128..5d14cef55c9f0 100644
--- a/modules/doctor/doctor.go
+++ b/modules/doctor/doctor.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -49,7 +50,11 @@ func initDBDisableConsole(ctx context.Context, disableConsole bool) error {
 
 	setting.NewXORMLogService(disableConsole)
 	if err := db.InitEngine(ctx); err != nil {
-		return fmt.Errorf("models.SetEngine: %v", err)
+		return fmt.Errorf("db.InitEngine: %w", err)
+	}
+	// some doctor sub-commands need to use git command
+	if err := git.InitFull(ctx); err != nil {
+		return fmt.Errorf("git.InitFull: %w", err)
 	}
 	return nil
 }
diff --git a/modules/doctor/fix16961.go b/modules/doctor/fix16961.go
index 92c44185055e1..307cfcd9ff877 100644
--- a/modules/doctor/fix16961.go
+++ b/modules/doctor/fix16961.go
@@ -216,7 +216,7 @@ func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed boo
 		return false, nil
 	}
 
-	switch unit.Type(repoUnit.Type) {
+	switch repoUnit.Type {
 	case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
 		cfg := &repo_model.UnitConfig{}
 		repoUnit.Config = cfg
diff --git a/modules/doctor/heads.go b/modules/doctor/heads.go
new file mode 100644
index 0000000000000..b1bfd50b202ca
--- /dev/null
+++ b/modules/doctor/heads.go
@@ -0,0 +1,89 @@
+// 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 doctor
+
+import (
+	"context"
+
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+)
+
+func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool) error {
+	numRepos := 0
+	numHeadsBroken := 0
+	numDefaultBranchesBroken := 0
+	numReposUpdated := 0
+	err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
+		numRepos++
+		_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
+
+		head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
+
+		// what we expect: default branch is valid, and HEAD points to it
+		if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
+			return nil
+		}
+
+		if headErr != nil {
+			numHeadsBroken++
+		}
+		if defaultBranchErr != nil {
+			numDefaultBranchesBroken++
+		}
+
+		// if default branch is broken, let the user fix that in the UI
+		if defaultBranchErr != nil {
+			logger.Warn("Default branch for %s/%s doesn't point to a valid commit", repo.OwnerName, repo.Name)
+			return nil
+		}
+
+		// if we're not autofixing, that's all we can do
+		if !autofix {
+			return nil
+		}
+
+		// otherwise, let's try fixing HEAD
+		err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
+		if err != nil {
+			logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
+			return nil
+		}
+		numReposUpdated++
+		return nil
+	})
+	if err != nil {
+		logger.Critical("Error when fixing repo HEADs: %v", err)
+	}
+
+	if autofix {
+		logger.Info("Out of %d repos, HEADs for %d are now fixed and HEADS for %d are still broken", numRepos, numReposUpdated, numDefaultBranchesBroken+numHeadsBroken-numReposUpdated)
+	} else {
+		if numHeadsBroken == 0 && numDefaultBranchesBroken == 0 {
+			logger.Info("All %d repos have their HEADs in the correct state", numRepos)
+		} else {
+			if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
+				logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
+			} else if numHeadsBroken != 0 && numDefaultBranchesBroken == 0 {
+				logger.Warn("HEADs are broken for %d/%d repos", numHeadsBroken, numRepos)
+			} else {
+				logger.Critical("Out of %d repos, HEADS are broken for %d and default branches are broken for %d", numRepos, numHeadsBroken, numDefaultBranchesBroken)
+			}
+		}
+	}
+
+	return err
+}
+
+func init() {
+	Register(&Check{
+		Title:     "Synchronize repo HEADs",
+		Name:      "synchronize-repo-heads",
+		IsDefault: true,
+		Run:       synchronizeRepoHeads,
+		Priority:  7,
+	})
+}
diff --git a/modules/doctor/mergebase.go b/modules/doctor/mergebase.go
index 46369290a13d7..b279c453f7995 100644
--- a/modules/doctor/mergebase.go
+++ b/modules/doctor/mergebase.go
@@ -44,17 +44,17 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
 
 			if !pr.HasMerged {
 				var err error
-				pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base", "--", pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath})
+				pr.MergeBase, _, err = git.NewCommand(ctx, "merge-base").AddDashesAndList(pr.BaseBranch, pr.GetGitRefName()).RunStdString(&git.RunOpts{Dir: repoPath})
 				if err != nil {
 					var err2 error
-					pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse", git.BranchPrefix+pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
+					pr.MergeBase, _, err2 = git.NewCommand(ctx, "rev-parse").AddDynamicArguments(git.BranchPrefix + pr.BaseBranch).RunStdString(&git.RunOpts{Dir: repoPath})
 					if err2 != nil {
 						logger.Warn("Unable to get merge base for PR ID %d, #%d onto %s in %s/%s. Error: %v & %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err, err2)
 						return nil
 					}
 				}
 			} else {
-				parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1", pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
+				parentsString, _, err := git.NewCommand(ctx, "rev-list", "--parents", "-n", "1").AddDynamicArguments(pr.MergedCommitID).RunStdString(&git.RunOpts{Dir: repoPath})
 				if err != nil {
 					logger.Warn("Unable to get parents for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
 					return nil
@@ -64,10 +64,10 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
 					return nil
 				}
 
-				args := append([]string{"merge-base", "--"}, parents[1:]...)
-				args = append(args, pr.GetGitRefName())
-
-				pr.MergeBase, _, err = git.NewCommand(ctx, args...).RunStdString(&git.RunOpts{Dir: repoPath})
+				refs := append([]string{}, parents[1:]...)
+				refs = append(refs, pr.GetGitRefName())
+				cmd := git.NewCommand(ctx, "merge-base").AddDashesAndList(refs...)
+				pr.MergeBase, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath})
 				if err != nil {
 					logger.Warn("Unable to get merge base for merged PR ID %d, #%d onto %s in %s/%s. Error: %v", pr.ID, pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, err)
 					return nil
@@ -78,7 +78,7 @@ func checkPRMergeBase(ctx context.Context, logger log.Logger, autofix bool) erro
 				if autofix {
 					if err := pr.UpdateCols("merge_base"); err != nil {
 						logger.Critical("Failed to update merge_base. ERROR: %v", err)
-						return fmt.Errorf("Failed to update merge_base. ERROR: %v", err)
+						return fmt.Errorf("Failed to update merge_base. ERROR: %w", err)
 					}
 				} else {
 					logger.Info("#%d onto %s in %s/%s: MergeBase should be %s but is %s", pr.Index, pr.BaseBranch, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, oldMergeBase, pr.MergeBase)
diff --git a/modules/doctor/misc.go b/modules/doctor/misc.go
index 9bee78303e1a5..277d66a177725 100644
--- a/modules/doctor/misc.go
+++ b/modules/doctor/misc.go
@@ -43,7 +43,7 @@ func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error
 	path, err := exec.LookPath(setting.ScriptType)
 	if err != nil {
 		logger.Critical("ScriptType \"%q\" is not on the current PATH. Error: %v", setting.ScriptType, err)
-		return fmt.Errorf("ScriptType \"%q\" is not on the current PATH. Error: %v", setting.ScriptType, err)
+		return fmt.Errorf("ScriptType \"%q\" is not on the current PATH. Error: %w", setting.ScriptType, err)
 	}
 	logger.Info("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)
 	return nil
@@ -54,13 +54,13 @@ func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error {
 		results, err := repository.CheckDelegateHooks(repo.RepoPath())
 		if err != nil {
 			logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
-			return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err)
+			return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err)
 		}
 		if len(results) > 0 && autofix {
 			logger.Warn("Regenerated hooks for %s", repo.FullName())
 			if err := repository.CreateDelegateHooks(repo.RepoPath()); err != nil {
 				logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
-				return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err)
+				return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err)
 			}
 		}
 		for _, result := range results {
@@ -189,6 +189,71 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err
 	return nil
 }
 
+func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) error {
+	numRepos := 0
+	numNeedUpdate := 0
+	numWritten := 0
+	if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
+		numRepos++
+
+		commitGraphExists := func() (bool, error) {
+			// Check commit-graph exists
+			commitGraphFile := path.Join(repo.RepoPath(), `objects/info/commit-graph`)
+			isExist, err := util.IsExist(commitGraphFile)
+			if err != nil {
+				logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err)
+				return false, err
+			}
+
+			if !isExist {
+				commitGraphsDir := path.Join(repo.RepoPath(), `objects/info/commit-graphs`)
+				isExist, err = util.IsExist(commitGraphsDir)
+				if err != nil {
+					logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err)
+					return false, err
+				}
+			}
+			return isExist, nil
+		}
+
+		isExist, err := commitGraphExists()
+		if err != nil {
+			return err
+		}
+		if !isExist {
+			numNeedUpdate++
+			if autofix {
+				if err := git.WriteCommitGraph(ctx, repo.RepoPath()); err != nil {
+					logger.Error("Unable to write commit-graph in %s. Error: %v", repo.FullName(), err)
+					return err
+				}
+				isExist, err := commitGraphExists()
+				if err != nil {
+					return err
+				}
+				if isExist {
+					numWritten++
+					logger.Info("Commit-graph written:    %s", repo.FullName())
+				} else {
+					logger.Warn("No commit-graph written: %s", repo.FullName())
+				}
+			}
+		}
+		return nil
+	}); err != nil {
+		logger.Critical("Unable to checkCommitGraph: %v", err)
+		return err
+	}
+
+	if autofix {
+		logger.Info("Wrote commit-graph files for %d of %d repositories.", numWritten, numRepos)
+	} else {
+		logger.Info("Checked %d repositories, %d without commit-graphs.", numRepos, numNeedUpdate)
+	}
+
+	return nil
+}
+
 func init() {
 	Register(&Check{
 		Title:     "Check if SCRIPT_TYPE is available",
@@ -225,4 +290,11 @@ func init() {
 		Run:       checkDaemonExport,
 		Priority:  8,
 	})
+	Register(&Check{
+		Title:     "Check commit-graphs",
+		Name:      "check-commit-graphs",
+		IsDefault: false,
+		Run:       checkCommitGraph,
+		Priority:  9,
+	})
 }
diff --git a/modules/doctor/paths.go b/modules/doctor/paths.go
index 22c095c2279bf..5a270454574bd 100644
--- a/modules/doctor/paths.go
+++ b/modules/doctor/paths.go
@@ -29,7 +29,7 @@ func checkConfigurationFile(logger log.Logger, autofix bool, fileOpts configurat
 		if os.IsNotExist(err) && autofix && fileOpts.IsDirectory {
 			if err := os.MkdirAll(fileOpts.Path, 0o777); err != nil {
 				logger.Error("    Directory does not exist and could not be created. ERROR: %v", err)
-				return fmt.Errorf("Configuration directory: \"%q\" does not exist and could not be created. ERROR: %v", fileOpts.Path, err)
+				return fmt.Errorf("Configuration directory: \"%q\" does not exist and could not be created. ERROR: %w", fileOpts.Path, err)
 			}
 			fi, err = os.Stat(fileOpts.Path)
 		}
@@ -37,7 +37,7 @@ func checkConfigurationFile(logger log.Logger, autofix bool, fileOpts configurat
 	if err != nil {
 		if fileOpts.Required {
 			logger.Error("    Is REQUIRED but is not accessible. ERROR: %v", err)
-			return fmt.Errorf("Configuration file \"%q\" is not accessible but is required. Error: %v", fileOpts.Path, err)
+			return fmt.Errorf("Configuration file \"%q\" is not accessible but is required. Error: %w", fileOpts.Path, err)
 		}
 		logger.Warn("    NOTICE: is not accessible (Error: %v)", err)
 		// this is a non-critical error
@@ -46,14 +46,14 @@ func checkConfigurationFile(logger log.Logger, autofix bool, fileOpts configurat
 
 	if fileOpts.IsDirectory && !fi.IsDir() {
 		logger.Error("    ERROR: not a directory")
-		return fmt.Errorf("Configuration directory \"%q\" is not a directory. Error: %v", fileOpts.Path, err)
+		return fmt.Errorf("Configuration directory \"%q\" is not a directory. Error: %w", fileOpts.Path, err)
 	} else if !fileOpts.IsDirectory && !fi.Mode().IsRegular() {
 		logger.Error("    ERROR: not a regular file")
-		return fmt.Errorf("Configuration file \"%q\" is not a regular file. Error: %v", fileOpts.Path, err)
+		return fmt.Errorf("Configuration file \"%q\" is not a regular file. Error: %w", fileOpts.Path, err)
 	} else if fileOpts.Writable {
 		if err := isWritableDir(fileOpts.Path); err != nil {
 			logger.Error("    ERROR: is required to be writable but is not writable: %v", err)
-			return fmt.Errorf("Configuration file \"%q\" is required to be writable but is not. Error: %v", fileOpts.Path, err)
+			return fmt.Errorf("Configuration file \"%q\" is required to be writable but is not. Error: %w", fileOpts.Path, err)
 		}
 	}
 	return nil
diff --git a/modules/doctor/usertype.go b/modules/doctor/usertype.go
index 34e12afe721bb..166e38bd247d7 100644
--- a/modules/doctor/usertype.go
+++ b/modules/doctor/usertype.go
@@ -7,19 +7,19 @@ package doctor
 import (
 	"context"
 
-	"code.gitea.io/gitea/models"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
 )
 
 func checkUserType(ctx context.Context, logger log.Logger, autofix bool) error {
-	count, err := models.CountWrongUserType()
+	count, err := user_model.CountWrongUserType()
 	if err != nil {
 		logger.Critical("Error: %v whilst counting wrong user types")
 		return err
 	}
 	if count > 0 {
 		if autofix {
-			if count, err = models.FixWrongUserType(); err != nil {
+			if count, err = user_model.FixWrongUserType(); err != nil {
 				logger.Critical("Error: %v whilst fixing wrong user types")
 				return err
 			}
diff --git a/modules/emoji/emoji_data.go b/modules/emoji/emoji_data.go
index 1fb08767bbc7d..1e14d3de6bb5a 100644
--- a/modules/emoji/emoji_data.go
+++ b/modules/emoji/emoji_data.go
@@ -4,9 +4,8 @@
 
 package emoji
 
-// Code generated by gen.go. DO NOT EDIT.
+// Code generated by build/generate-emoji.go. DO NOT EDIT.
 // Sourced from https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json
-//
 var GemojiData = Gemoji{
 	{"\U0001f44d", "thumbs up", []string{"+1", "thumbsup"}, "6.0", true},
 	{"\U0001f44e", "thumbs down", []string{"-1", "thumbsdown"}, "6.0", true},
@@ -129,7 +128,7 @@ var GemojiData = Gemoji{
 	{"\U0001f50b", "battery", []string{"battery"}, "6.0", false},
 	{"\U0001f3d6\ufe0f", "beach with umbrella", []string{"beach_umbrella"}, "7.0", false},
 	{"\U0001f43b", "bear", []string{"bear"}, "6.0", false},
-	{"\U0001f9d4", "man: beard", []string{"bearded_person"}, "11.0", true},
+	{"\U0001f9d4", "person: beard", []string{"bearded_person"}, "11.0", true},
 	{"\U0001f6cf\ufe0f", "bed", []string{"bed"}, "7.0", false},
 	{"\U0001f41d", "honeybee", []string{"bee", "honeybee"}, "6.0", false},
 	{"\U0001f37a", "beer mug", []string{"beer"}, "6.0", false},
@@ -377,14 +376,14 @@ var GemojiData = Gemoji{
 	{"\U0001f1e8\U0001f1ee", "flag: Côte d’Ivoire", []string{"cote_divoire"}, "6.0", false},
 	{"\U0001f6cb\ufe0f", "couch and lamp", []string{"couch_and_lamp"}, "7.0", false},
 	{"\U0001f46b", "woman and man holding hands", []string{"couple"}, "6.0", true},
-	{"\U0001f491", "couple with heart", []string{"couple_with_heart"}, "6.0", false},
-	{"\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man", []string{"couple_with_heart_man_man"}, "6.0", false},
-	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man", []string{"couple_with_heart_woman_man"}, "11.0", false},
-	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman", []string{"couple_with_heart_woman_woman"}, "6.0", false},
-	{"\U0001f48f", "kiss", []string{"couplekiss"}, "6.0", false},
-	{"\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man", []string{"couplekiss_man_man"}, "6.0", false},
-	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man", []string{"couplekiss_man_woman"}, "11.0", false},
-	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman", []string{"couplekiss_woman_woman"}, "6.0", false},
+	{"\U0001f491", "couple with heart", []string{"couple_with_heart"}, "6.0", true},
+	{"\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man", []string{"couple_with_heart_man_man"}, "6.0", true},
+	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man", []string{"couple_with_heart_woman_man"}, "11.0", true},
+	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman", []string{"couple_with_heart_woman_woman"}, "6.0", true},
+	{"\U0001f48f", "kiss", []string{"couplekiss"}, "6.0", true},
+	{"\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man", []string{"couplekiss_man_man"}, "6.0", true},
+	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man", []string{"couplekiss_man_woman"}, "11.0", true},
+	{"\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman", []string{"couplekiss_woman_woman"}, "6.0", true},
 	{"\U0001f42e", "cow face", []string{"cow"}, "6.0", false},
 	{"\U0001f404", "cow", []string{"cow2"}, "6.0", false},
 	{"\U0001f920", "cowboy hat face", []string{"cowboy_hat_face"}, "9.0", false},
@@ -429,7 +428,7 @@ var GemojiData = Gemoji{
 	{"\U0001f46f\u200d\u2640\ufe0f", "women with bunny ears", []string{"dancing_women"}, "11.0", false},
 	{"\U0001f361", "dango", []string{"dango"}, "6.0", false},
 	{"\U0001f576\ufe0f", "sunglasses", []string{"dark_sunglasses"}, "7.0", false},
-	{"\U0001f3af", "direct hit", []string{"dart"}, "6.0", false},
+	{"\U0001f3af", "bullseye", []string{"dart"}, "6.0", false},
 	{"\U0001f4a8", "dashing away", []string{"dash"}, "6.0", false},
 	{"\U0001f4c5", "calendar", []string{"date"}, "6.0", false},
 	{"\U0001f1e9\U0001f1ea", "flag: Germany", []string{"de"}, "6.0", false},
@@ -453,7 +452,7 @@ var GemojiData = Gemoji{
 	{"\U0001f93f", "diving mask", []string{"diving_mask"}, "12.0", false},
 	{"\U0001fa94", "diya lamp", []string{"diya_lamp"}, "12.0", false},
 	{"\U0001f4ab", "dizzy", []string{"dizzy"}, "6.0", false},
-	{"\U0001f635", "dizzy face", []string{"dizzy_face"}, "6.0", false},
+	{"\U0001f635", "knocked-out face", []string{"dizzy_face"}, "6.0", false},
 	{"\U0001f1e9\U0001f1ef", "flag: Djibouti", []string{"djibouti"}, "6.0", false},
 	{"\U0001f9ec", "dna", []string{"dna"}, "11.0", false},
 	{"\U0001f6af", "no littering", []string{"do_not_litter"}, "6.0", false},
@@ -478,7 +477,6 @@ var GemojiData = Gemoji{
 	{"\U0001f986", "duck", []string{"duck"}, "9.0", false},
 	{"\U0001f95f", "dumpling", []string{"dumpling"}, "11.0", false},
 	{"\U0001f4c0", "dvd", []string{"dvd"}, "6.0", false},
-	{"\U0001f4e7", "e-mail", []string{"e-mail"}, "6.0", false},
 	{"\U0001f985", "eagle", []string{"eagle"}, "9.0", false},
 	{"\U0001f442", "ear", []string{"ear"}, "6.0", true},
 	{"\U0001f33e", "sheaf of rice", []string{"ear_of_rice"}, "6.0", false},
@@ -500,9 +498,10 @@ var GemojiData = Gemoji{
 	{"\U0001f9dd", "elf", []string{"elf"}, "11.0", true},
 	{"\U0001f9dd\u200d\u2642\ufe0f", "man elf", []string{"elf_man"}, "11.0", true},
 	{"\U0001f9dd\u200d\u2640\ufe0f", "woman elf", []string{"elf_woman"}, "11.0", true},
-	{"\u2709\ufe0f", "envelope", []string{"email", "envelope"}, "", false},
+	{"\U0001f4e7", "e-mail", []string{"email", "e-mail"}, "6.0", false},
 	{"\U0001f51a", "END arrow", []string{"end"}, "6.0", false},
 	{"\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", "flag: England", []string{"england"}, "11.0", false},
+	{"\u2709\ufe0f", "envelope", []string{"envelope"}, "", false},
 	{"\U0001f4e9", "envelope with arrow", []string{"envelope_with_arrow"}, "6.0", false},
 	{"\U0001f1ec\U0001f1f6", "flag: Equatorial Guinea", []string{"equatorial_guinea"}, "6.0", false},
 	{"\U0001f1ea\U0001f1f7", "flag: Eritrea", []string{"eritrea"}, "6.0", false},
@@ -514,7 +513,7 @@ var GemojiData = Gemoji{
 	{"\U0001f3f0", "castle", []string{"european_castle"}, "6.0", false},
 	{"\U0001f3e4", "post office", []string{"european_post_office"}, "6.0", false},
 	{"\U0001f332", "evergreen tree", []string{"evergreen_tree"}, "6.0", false},
-	{"\u2757", "exclamation mark", []string{"exclamation", "heavy_exclamation_mark"}, "5.2", false},
+	{"\u2757", "red exclamation mark", []string{"exclamation", "heavy_exclamation_mark"}, "5.2", false},
 	{"\U0001f92f", "exploding head", []string{"exploding_head"}, "11.0", false},
 	{"\U0001f611", "expressionless face", []string{"expressionless"}, "6.1", false},
 	{"\U0001f441\ufe0f", "eye", []string{"eye"}, "7.0", false},
@@ -689,7 +688,7 @@ var GemojiData = Gemoji{
 	{"\U0001f1ec\U0001f1f3", "flag: Guinea", []string{"guinea"}, "6.0", false},
 	{"\U0001f1ec\U0001f1fc", "flag: Guinea-Bissau", []string{"guinea_bissau"}, "6.0", false},
 	{"\U0001f3b8", "guitar", []string{"guitar"}, "6.0", false},
-	{"\U0001f52b", "pistol", []string{"gun"}, "6.0", false},
+	{"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false},
 	{"\U0001f1ec\U0001f1fe", "flag: Guyana", []string{"guyana"}, "6.0", false},
 	{"\U0001f487", "person getting haircut", []string{"haircut"}, "6.0", true},
 	{"\U0001f487\u200d\u2642\ufe0f", "man getting haircut", []string{"haircut_man"}, "6.0", true},
@@ -1228,7 +1227,7 @@ var GemojiData = Gemoji{
 	{"\U0001f4cc", "pushpin", []string{"pushpin"}, "6.0", false},
 	{"\U0001f6ae", "litter in bin sign", []string{"put_litter_in_its_place"}, "6.0", false},
 	{"\U0001f1f6\U0001f1e6", "flag: Qatar", []string{"qatar"}, "6.0", false},
-	{"\u2753", "question mark", []string{"question"}, "6.0", false},
+	{"\u2753", "red question mark", []string{"question"}, "6.0", false},
 	{"\U0001f430", "rabbit face", []string{"rabbit"}, "6.0", false},
 	{"\U0001f407", "rabbit", []string{"rabbit2"}, "6.0", false},
 	{"\U0001f99d", "raccoon", []string{"raccoon"}, "11.0", false},
@@ -1751,61 +1750,61 @@ var GemojiData = Gemoji{
 	{"\U0001f44d\U0001f3fc", "thumbs up: Medium-Light Skin Tone", []string{"+1_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44d\U0001f3fd", "thumbs up: Medium Skin Tone", []string{"+1_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f44d\U0001f3fe", "thumbs up: Medium-Dark Skin Tone", []string{"+1_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f44e\U0001f3ff", "thumbs down: Dark Skin Tone", []string{"-1_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f44e\U0001f3fb", "thumbs down: Light Skin Tone", []string{"-1_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44e\U0001f3fc", "thumbs down: Medium-Light Skin Tone", []string{"-1_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44e\U0001f3fd", "thumbs down: Medium Skin Tone", []string{"-1_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f44e\U0001f3fe", "thumbs down: Medium-Dark Skin Tone", []string{"-1_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f44e\U0001f3ff", "thumbs down: Dark Skin Tone", []string{"-1_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc", "person: Medium-Light Skin Tone", []string{"adult_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd", "person: Medium Skin Tone", []string{"adult_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe", "person: Medium-Dark Skin Tone", []string{"adult_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff", "person: Dark Skin Tone", []string{"adult_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb", "person: Light Skin Tone", []string{"adult_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc", "person: Medium-Light Skin Tone", []string{"adult_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f47c\U0001f3fb", "baby angel: Light Skin Tone", []string{"angel_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f47c\U0001f3fc", "baby angel: Medium-Light Skin Tone", []string{"angel_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f47c\U0001f3fd", "baby angel: Medium Skin Tone", []string{"angel_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f47c\U0001f3fe", "baby angel: Medium-Dark Skin Tone", []string{"angel_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f47c\U0001f3ff", "baby angel: Dark Skin Tone", []string{"angel_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fe\u200d\U0001f3a8", "artist: Medium-Dark Skin Tone", []string{"artist_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\U0001f3a8", "artist: Dark Skin Tone", []string{"artist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f3a8", "artist: Light Skin Tone", []string{"artist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f3a8", "artist: Medium-Light Skin Tone", []string{"artist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f3a8", "artist: Medium Skin Tone", []string{"artist_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f680", "astronaut: Light Skin Tone", []string{"astronaut_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fe\u200d\U0001f3a8", "artist: Medium-Dark Skin Tone", []string{"artist_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\U0001f3a8", "artist: Dark Skin Tone", []string{"artist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f680", "astronaut: Medium-Light Skin Tone", []string{"astronaut_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f680", "astronaut: Medium Skin Tone", []string{"astronaut_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f680", "astronaut: Medium-Dark Skin Tone", []string{"astronaut_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f680", "astronaut: Dark Skin Tone", []string{"astronaut_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f680", "astronaut: Light Skin Tone", []string{"astronaut_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f476\U0001f3fb", "baby: Light Skin Tone", []string{"baby_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f476\U0001f3fc", "baby: Medium-Light Skin Tone", []string{"baby_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f476\U0001f3fd", "baby: Medium Skin Tone", []string{"baby_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f476\U0001f3fe", "baby: Medium-Dark Skin Tone", []string{"baby_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f476\U0001f3ff", "baby: Dark Skin Tone", []string{"baby_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f9b2", "man: bald: Medium-Dark Skin Tone", []string{"bald_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f9b2", "man: bald: Dark Skin Tone", []string{"bald_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f9b2", "man: bald: Light Skin Tone", []string{"bald_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f9b2", "man: bald: Medium-Light Skin Tone", []string{"bald_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9b2", "man: bald: Medium Skin Tone", []string{"bald_man_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f9b2", "man: bald: Medium-Dark Skin Tone", []string{"bald_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f9b2", "man: bald: Dark Skin Tone", []string{"bald_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f9b2", "woman: bald: Dark Skin Tone", []string{"bald_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\U0001f9b2", "woman: bald: Light Skin Tone", []string{"bald_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9b2", "woman: bald: Medium-Light Skin Tone", []string{"bald_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9b2", "woman: bald: Medium Skin Tone", []string{"bald_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f9b2", "woman: bald: Medium-Dark Skin Tone", []string{"bald_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f9b2", "woman: bald: Dark Skin Tone", []string{"bald_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\U0001f9b2", "woman: bald: Light Skin Tone", []string{"bald_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f6c0\U0001f3fe", "person taking bath: Medium-Dark Skin Tone", []string{"bath_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6c0\U0001f3ff", "person taking bath: Dark Skin Tone", []string{"bath_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6c0\U0001f3fb", "person taking bath: Light Skin Tone", []string{"bath_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6c0\U0001f3fc", "person taking bath: Medium-Light Skin Tone", []string{"bath_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6c0\U0001f3fd", "person taking bath: Medium Skin Tone", []string{"bath_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f6c0\U0001f3fe", "person taking bath: Medium-Dark Skin Tone", []string{"bath_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6c0\U0001f3ff", "person taking bath: Dark Skin Tone", []string{"bath_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d4\U0001f3fb", "man: beard: Light Skin Tone", []string{"bearded_person_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d4\U0001f3fc", "man: beard: Medium-Light Skin Tone", []string{"bearded_person_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d4\U0001f3fd", "man: beard: Medium Skin Tone", []string{"bearded_person_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d4\U0001f3fe", "man: beard: Medium-Dark Skin Tone", []string{"bearded_person_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d4\U0001f3ff", "man: beard: Dark Skin Tone", []string{"bearded_person_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d4\U0001f3fd", "person: beard: Medium Skin Tone", []string{"bearded_person_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d4\U0001f3fe", "person: beard: Medium-Dark Skin Tone", []string{"bearded_person_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d4\U0001f3ff", "person: beard: Dark Skin Tone", []string{"bearded_person_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d4\U0001f3fb", "person: beard: Light Skin Tone", []string{"bearded_person_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d4\U0001f3fc", "person: beard: Medium-Light Skin Tone", []string{"bearded_person_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b4\U0001f3fd", "person biking: Medium Skin Tone", []string{"bicyclist_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b4\U0001f3fe", "person biking: Medium-Dark Skin Tone", []string{"bicyclist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3ff", "person biking: Dark Skin Tone", []string{"bicyclist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3fb", "person biking: Light Skin Tone", []string{"bicyclist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3fc", "person biking: Medium-Light Skin Tone", []string{"bicyclist_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b4\U0001f3fd", "person biking: Medium Skin Tone", []string{"bicyclist_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b4\U0001f3fe", "person biking: Medium-Dark Skin Tone", []string{"bicyclist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f", "man biking: Medium-Dark Skin Tone", []string{"biking_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f", "man biking: Dark Skin Tone", []string{"biking_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f", "man biking: Light Skin Tone", []string{"biking_man_Light_Skin_Tone"}, "12.0", false},
@@ -1821,21 +1820,21 @@ var GemojiData = Gemoji{
 	{"\U0001f471\U0001f3fd\u200d\u2642\ufe0f", "man: blond hair: Medium Skin Tone", []string{"blond_haired_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fe\u200d\u2642\ufe0f", "man: blond hair: Medium-Dark Skin Tone", []string{"blond_haired_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3ff\u200d\u2642\ufe0f", "man: blond hair: Dark Skin Tone", []string{"blond_haired_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f471\U0001f3fb", "person: blond hair: Light Skin Tone", []string{"blond_haired_person_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f471\U0001f3fc", "person: blond hair: Medium-Light Skin Tone", []string{"blond_haired_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fd", "person: blond hair: Medium Skin Tone", []string{"blond_haired_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fe", "person: blond hair: Medium-Dark Skin Tone", []string{"blond_haired_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3ff", "person: blond hair: Dark Skin Tone", []string{"blond_haired_person_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f471\U0001f3fb", "person: blond hair: Light Skin Tone", []string{"blond_haired_person_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f471\U0001f3fc", "person: blond hair: Medium-Light Skin Tone", []string{"blond_haired_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fb\u200d\u2640\ufe0f", "woman: blond hair: Light Skin Tone", []string{"blond_haired_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fc\u200d\u2640\ufe0f", "woman: blond hair: Medium-Light Skin Tone", []string{"blond_haired_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fd\u200d\u2640\ufe0f", "woman: blond hair: Medium Skin Tone", []string{"blond_haired_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3fe\u200d\u2640\ufe0f", "woman: blond hair: Medium-Dark Skin Tone", []string{"blond_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f471\U0001f3ff\u200d\u2640\ufe0f", "woman: blond hair: Dark Skin Tone", []string{"blond_haired_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\u26f9\U0001f3fe\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Medium-Dark Skin Tone", []string{"bouncing_ball_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\u26f9\U0001f3ff\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Dark Skin Tone", []string{"bouncing_ball_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fb\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Light Skin Tone", []string{"bouncing_ball_man_Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fc\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Medium-Light Skin Tone", []string{"bouncing_ball_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fd\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Medium Skin Tone", []string{"bouncing_ball_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\u26f9\U0001f3fe\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Medium-Dark Skin Tone", []string{"bouncing_ball_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\u26f9\U0001f3ff\ufe0f\u200d\u2642\ufe0f", "man bouncing ball: Dark Skin Tone", []string{"bouncing_ball_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fb\ufe0f", "person bouncing ball: Light Skin Tone", []string{"bouncing_ball_person_Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fc\ufe0f", "person bouncing ball: Medium-Light Skin Tone", []string{"bouncing_ball_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fd\ufe0f", "person bouncing ball: Medium Skin Tone", []string{"bouncing_ball_person_Medium_Skin_Tone"}, "12.0", false},
@@ -1846,31 +1845,31 @@ var GemojiData = Gemoji{
 	{"\u26f9\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman bouncing ball: Light Skin Tone", []string{"bouncing_ball_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman bouncing ball: Medium-Light Skin Tone", []string{"bouncing_ball_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u26f9\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman bouncing ball: Medium Skin Tone", []string{"bouncing_ball_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f647\U0001f3fb", "person bowing: Light Skin Tone", []string{"bow_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f647\U0001f3fc", "person bowing: Medium-Light Skin Tone", []string{"bow_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fd", "person bowing: Medium Skin Tone", []string{"bow_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fe", "person bowing: Medium-Dark Skin Tone", []string{"bow_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3ff", "person bowing: Dark Skin Tone", []string{"bow_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f647\U0001f3fb", "person bowing: Light Skin Tone", []string{"bow_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f647\U0001f3fc", "person bowing: Medium-Light Skin Tone", []string{"bow_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3ff\u200d\u2642\ufe0f", "man bowing: Dark Skin Tone", []string{"bowing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fb\u200d\u2642\ufe0f", "man bowing: Light Skin Tone", []string{"bowing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fc\u200d\u2642\ufe0f", "man bowing: Medium-Light Skin Tone", []string{"bowing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fd\u200d\u2642\ufe0f", "man bowing: Medium Skin Tone", []string{"bowing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fe\u200d\u2642\ufe0f", "man bowing: Medium-Dark Skin Tone", []string{"bowing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f647\U0001f3fe\u200d\u2640\ufe0f", "woman bowing: Medium-Dark Skin Tone", []string{"bowing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f647\U0001f3ff\u200d\u2640\ufe0f", "woman bowing: Dark Skin Tone", []string{"bowing_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fb\u200d\u2640\ufe0f", "woman bowing: Light Skin Tone", []string{"bowing_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fc\u200d\u2640\ufe0f", "woman bowing: Medium-Light Skin Tone", []string{"bowing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f647\U0001f3fd\u200d\u2640\ufe0f", "woman bowing: Medium Skin Tone", []string{"bowing_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f647\U0001f3fe\u200d\u2640\ufe0f", "woman bowing: Medium-Dark Skin Tone", []string{"bowing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f647\U0001f3ff\u200d\u2640\ufe0f", "woman bowing: Dark Skin Tone", []string{"bowing_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f466\U0001f3fb", "boy: Light Skin Tone", []string{"boy_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f466\U0001f3fc", "boy: Medium-Light Skin Tone", []string{"boy_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f466\U0001f3fd", "boy: Medium Skin Tone", []string{"boy_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f466\U0001f3fe", "boy: Medium-Dark Skin Tone", []string{"boy_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f466\U0001f3ff", "boy: Dark Skin Tone", []string{"boy_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f931\U0001f3fe", "breast-feeding: Medium-Dark Skin Tone", []string{"breast_feeding_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f931\U0001f3ff", "breast-feeding: Dark Skin Tone", []string{"breast_feeding_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f931\U0001f3fb", "breast-feeding: Light Skin Tone", []string{"breast_feeding_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f931\U0001f3fc", "breast-feeding: Medium-Light Skin Tone", []string{"breast_feeding_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f931\U0001f3fd", "breast-feeding: Medium Skin Tone", []string{"breast_feeding_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f931\U0001f3fe", "breast-feeding: Medium-Dark Skin Tone", []string{"breast_feeding_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f931\U0001f3ff", "breast-feeding: Dark Skin Tone", []string{"breast_feeding_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f574\U0001f3fb\ufe0f", "person in suit levitating: Light Skin Tone", []string{"business_suit_levitating_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f574\U0001f3fc\ufe0f", "person in suit levitating: Medium-Light Skin Tone", []string{"business_suit_levitating_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f574\U0001f3fd\ufe0f", "person in suit levitating: Medium Skin Tone", []string{"business_suit_levitating_Medium_Skin_Tone"}, "12.0", false},
@@ -1881,116 +1880,156 @@ var GemojiData = Gemoji{
 	{"\U0001f919\U0001f3fd", "call me hand: Medium Skin Tone", []string{"call_me_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f919\U0001f3fe", "call me hand: Medium-Dark Skin Tone", []string{"call_me_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f919\U0001f3ff", "call me hand: Dark Skin Tone", []string{"call_me_hand_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f938\U0001f3ff", "person cartwheeling: Dark Skin Tone", []string{"cartwheeling_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f938\U0001f3fb", "person cartwheeling: Light Skin Tone", []string{"cartwheeling_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fc", "person cartwheeling: Medium-Light Skin Tone", []string{"cartwheeling_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fd", "person cartwheeling: Medium Skin Tone", []string{"cartwheeling_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fe", "person cartwheeling: Medium-Dark Skin Tone", []string{"cartwheeling_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f938\U0001f3ff", "person cartwheeling: Dark Skin Tone", []string{"cartwheeling_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f938\U0001f3fb", "person cartwheeling: Light Skin Tone", []string{"cartwheeling_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d2\U0001f3fb", "child: Light Skin Tone", []string{"child_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d2\U0001f3fc", "child: Medium-Light Skin Tone", []string{"child_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d2\U0001f3fd", "child: Medium Skin Tone", []string{"child_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d2\U0001f3fe", "child: Medium-Dark Skin Tone", []string{"child_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d2\U0001f3ff", "child: Dark Skin Tone", []string{"child_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d2\U0001f3fb", "child: Light Skin Tone", []string{"child_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d2\U0001f3fc", "child: Medium-Light Skin Tone", []string{"child_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f44f\U0001f3fb", "clapping hands: Light Skin Tone", []string{"clap_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f44f\U0001f3fc", "clapping hands: Medium-Light Skin Tone", []string{"clap_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44f\U0001f3fd", "clapping hands: Medium Skin Tone", []string{"clap_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f44f\U0001f3fe", "clapping hands: Medium-Dark Skin Tone", []string{"clap_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f44f\U0001f3ff", "clapping hands: Dark Skin Tone", []string{"clap_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f44f\U0001f3fb", "clapping hands: Light Skin Tone", []string{"clap_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f44f\U0001f3fc", "clapping hands: Medium-Light Skin Tone", []string{"clap_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d7\U0001f3fb", "person climbing: Light Skin Tone", []string{"climbing_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d7\U0001f3fc", "person climbing: Medium-Light Skin Tone", []string{"climbing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fd", "person climbing: Medium Skin Tone", []string{"climbing_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fe", "person climbing: Medium-Dark Skin Tone", []string{"climbing_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3ff", "person climbing: Dark Skin Tone", []string{"climbing_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d7\U0001f3fb", "person climbing: Light Skin Tone", []string{"climbing_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d7\U0001f3fc", "person climbing: Medium-Light Skin Tone", []string{"climbing_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f", "man climbing: Dark Skin Tone", []string{"climbing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f", "man climbing: Light Skin Tone", []string{"climbing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f", "man climbing: Medium-Light Skin Tone", []string{"climbing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f", "man climbing: Medium Skin Tone", []string{"climbing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f", "man climbing: Medium-Dark Skin Tone", []string{"climbing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f", "man climbing: Dark Skin Tone", []string{"climbing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f", "woman climbing: Light Skin Tone", []string{"climbing_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f", "woman climbing: Medium-Light Skin Tone", []string{"climbing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f", "woman climbing: Medium Skin Tone", []string{"climbing_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f", "woman climbing: Medium-Dark Skin Tone", []string{"climbing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f", "woman climbing: Dark Skin Tone", []string{"climbing_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f477\U0001f3fb", "construction worker: Light Skin Tone", []string{"construction_worker_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f477\U0001f3fc", "construction worker: Medium-Light Skin Tone", []string{"construction_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fd", "construction worker: Medium Skin Tone", []string{"construction_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fe", "construction worker: Medium-Dark Skin Tone", []string{"construction_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3ff", "construction worker: Dark Skin Tone", []string{"construction_worker_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f477\U0001f3fb", "construction worker: Light Skin Tone", []string{"construction_worker_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f477\U0001f3fc", "construction worker: Medium-Light Skin Tone", []string{"construction_worker_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f477\U0001f3ff\u200d\u2642\ufe0f", "man construction worker: Dark Skin Tone", []string{"construction_worker_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fb\u200d\u2642\ufe0f", "man construction worker: Light Skin Tone", []string{"construction_worker_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fc\u200d\u2642\ufe0f", "man construction worker: Medium-Light Skin Tone", []string{"construction_worker_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fd\u200d\u2642\ufe0f", "man construction worker: Medium Skin Tone", []string{"construction_worker_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fe\u200d\u2642\ufe0f", "man construction worker: Medium-Dark Skin Tone", []string{"construction_worker_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f477\U0001f3ff\u200d\u2642\ufe0f", "man construction worker: Dark Skin Tone", []string{"construction_worker_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f477\U0001f3ff\u200d\u2640\ufe0f", "woman construction worker: Dark Skin Tone", []string{"construction_worker_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fb\u200d\u2640\ufe0f", "woman construction worker: Light Skin Tone", []string{"construction_worker_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fc\u200d\u2640\ufe0f", "woman construction worker: Medium-Light Skin Tone", []string{"construction_worker_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fd\u200d\u2640\ufe0f", "woman construction worker: Medium Skin Tone", []string{"construction_worker_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f477\U0001f3fe\u200d\u2640\ufe0f", "woman construction worker: Medium-Dark Skin Tone", []string{"construction_worker_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f477\U0001f3ff\u200d\u2640\ufe0f", "woman construction worker: Dark Skin Tone", []string{"construction_worker_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f373", "cook: Light Skin Tone", []string{"cook_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f373", "cook: Medium-Light Skin Tone", []string{"cook_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f373", "cook: Medium Skin Tone", []string{"cook_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f373", "cook: Medium-Dark Skin Tone", []string{"cook_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f373", "cook: Dark Skin Tone", []string{"cook_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f373", "cook: Light Skin Tone", []string{"cook_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f373", "cook: Medium-Light Skin Tone", []string{"cook_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46b\U0001f3fb", "woman and man holding hands: Light Skin Tone", []string{"couple_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46b\U0001f3fc", "woman and man holding hands: Medium-Light Skin Tone", []string{"couple_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46b\U0001f3fd", "woman and man holding hands: Medium Skin Tone", []string{"couple_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f46b\U0001f3fe", "woman and man holding hands: Medium-Dark Skin Tone", []string{"couple_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f46b\U0001f3ff", "woman and man holding hands: Dark Skin Tone", []string{"couple_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f491\U0001f3fd", "couple with heart: Medium Skin Tone", []string{"couple_with_heart_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f491\U0001f3fe", "couple with heart: Medium-Dark Skin Tone", []string{"couple_with_heart_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f491\U0001f3ff", "couple with heart: Dark Skin Tone", []string{"couple_with_heart_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f491\U0001f3fb", "couple with heart: Light Skin Tone", []string{"couple_with_heart_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f491\U0001f3fc", "couple with heart: Medium-Light Skin Tone", []string{"couple_with_heart_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man: Medium Skin Tone", []string{"couple_with_heart_man_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man: Medium-Dark Skin Tone", []string{"couple_with_heart_man_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man: Dark Skin Tone", []string{"couple_with_heart_man_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man: Light Skin Tone", []string{"couple_with_heart_man_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: man, man: Medium-Light Skin Tone", []string{"couple_with_heart_man_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man: Light Skin Tone", []string{"couple_with_heart_woman_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man: Medium-Light Skin Tone", []string{"couple_with_heart_woman_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man: Medium Skin Tone", []string{"couple_with_heart_woman_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man: Medium-Dark Skin Tone", []string{"couple_with_heart_woman_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468", "couple with heart: woman, man: Dark Skin Tone", []string{"couple_with_heart_woman_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman: Medium Skin Tone", []string{"couple_with_heart_woman_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman: Medium-Dark Skin Tone", []string{"couple_with_heart_woman_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman: Dark Skin Tone", []string{"couple_with_heart_woman_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman: Light Skin Tone", []string{"couple_with_heart_woman_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469", "couple with heart: woman, woman: Medium-Light Skin Tone", []string{"couple_with_heart_woman_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f48f\U0001f3fe", "kiss: Medium-Dark Skin Tone", []string{"couplekiss_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f48f\U0001f3ff", "kiss: Dark Skin Tone", []string{"couplekiss_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f48f\U0001f3fb", "kiss: Light Skin Tone", []string{"couplekiss_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f48f\U0001f3fc", "kiss: Medium-Light Skin Tone", []string{"couplekiss_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f48f\U0001f3fd", "kiss: Medium Skin Tone", []string{"couplekiss_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man: Dark Skin Tone", []string{"couplekiss_man_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man: Light Skin Tone", []string{"couplekiss_man_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man: Medium-Light Skin Tone", []string{"couplekiss_man_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man: Medium Skin Tone", []string{"couplekiss_man_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: man, man: Medium-Dark Skin Tone", []string{"couplekiss_man_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man: Light Skin Tone", []string{"couplekiss_man_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man: Medium-Light Skin Tone", []string{"couplekiss_man_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man: Medium Skin Tone", []string{"couplekiss_man_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man: Medium-Dark Skin Tone", []string{"couplekiss_man_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468", "kiss: woman, man: Dark Skin Tone", []string{"couplekiss_man_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman: Light Skin Tone", []string{"couplekiss_woman_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman: Medium-Light Skin Tone", []string{"couplekiss_woman_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman: Medium Skin Tone", []string{"couplekiss_woman_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman: Medium-Dark Skin Tone", []string{"couplekiss_woman_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469", "kiss: woman, woman: Dark Skin Tone", []string{"couplekiss_woman_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f91e\U0001f3fe", "crossed fingers: Medium-Dark Skin Tone", []string{"crossed_fingers_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f91e\U0001f3ff", "crossed fingers: Dark Skin Tone", []string{"crossed_fingers_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f91e\U0001f3fb", "crossed fingers: Light Skin Tone", []string{"crossed_fingers_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91e\U0001f3fc", "crossed fingers: Medium-Light Skin Tone", []string{"crossed_fingers_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91e\U0001f3fd", "crossed fingers: Medium Skin Tone", []string{"crossed_fingers_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f91e\U0001f3fe", "crossed fingers: Medium-Dark Skin Tone", []string{"crossed_fingers_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fd\u200d\U0001f9b1", "man: curly hair: Medium Skin Tone", []string{"curly_haired_man_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f9b1", "man: curly hair: Medium-Dark Skin Tone", []string{"curly_haired_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f9b1", "man: curly hair: Dark Skin Tone", []string{"curly_haired_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f9b1", "man: curly hair: Light Skin Tone", []string{"curly_haired_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f9b1", "man: curly hair: Medium-Light Skin Tone", []string{"curly_haired_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fd\u200d\U0001f9b1", "man: curly hair: Medium Skin Tone", []string{"curly_haired_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f9b1", "man: curly hair: Medium-Dark Skin Tone", []string{"curly_haired_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f9b1", "woman: curly hair: Dark Skin Tone", []string{"curly_haired_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9b1", "woman: curly hair: Light Skin Tone", []string{"curly_haired_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9b1", "woman: curly hair: Medium-Light Skin Tone", []string{"curly_haired_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9b1", "woman: curly hair: Medium Skin Tone", []string{"curly_haired_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f9b1", "woman: curly hair: Medium-Dark Skin Tone", []string{"curly_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f9b1", "woman: curly hair: Dark Skin Tone", []string{"curly_haired_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f", "deaf man: Dark Skin Tone", []string{"deaf_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f", "deaf man: Light Skin Tone", []string{"deaf_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f", "deaf man: Medium-Light Skin Tone", []string{"deaf_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f", "deaf man: Medium Skin Tone", []string{"deaf_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f", "deaf man: Medium-Dark Skin Tone", []string{"deaf_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f", "deaf man: Dark Skin Tone", []string{"deaf_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cf\U0001f3fb", "deaf person: Light Skin Tone", []string{"deaf_person_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cf\U0001f3fc", "deaf person: Medium-Light Skin Tone", []string{"deaf_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fd", "deaf person: Medium Skin Tone", []string{"deaf_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fe", "deaf person: Medium-Dark Skin Tone", []string{"deaf_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3ff", "deaf person: Dark Skin Tone", []string{"deaf_person_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cf\U0001f3fb", "deaf person: Light Skin Tone", []string{"deaf_person_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cf\U0001f3fc", "deaf person: Medium-Light Skin Tone", []string{"deaf_person_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f", "deaf woman: Medium-Dark Skin Tone", []string{"deaf_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f", "deaf woman: Dark Skin Tone", []string{"deaf_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f", "deaf woman: Light Skin Tone", []string{"deaf_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f", "deaf woman: Medium-Light Skin Tone", []string{"deaf_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f", "deaf woman: Medium Skin Tone", []string{"deaf_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f", "deaf woman: Medium-Dark Skin Tone", []string{"deaf_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f", "deaf woman: Dark Skin Tone", []string{"deaf_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f575\U0001f3fe\ufe0f", "detective: Medium-Dark Skin Tone", []string{"detective_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f575\U0001f3ff\ufe0f", "detective: Dark Skin Tone", []string{"detective_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fb\ufe0f", "detective: Light Skin Tone", []string{"detective_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fc\ufe0f", "detective: Medium-Light Skin Tone", []string{"detective_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fd\ufe0f", "detective: Medium Skin Tone", []string{"detective_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f442\U0001f3fc", "ear: Medium-Light Skin Tone", []string{"ear_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f442\U0001f3fd", "ear: Medium Skin Tone", []string{"ear_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f575\U0001f3fe\ufe0f", "detective: Medium-Dark Skin Tone", []string{"detective_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f575\U0001f3ff\ufe0f", "detective: Dark Skin Tone", []string{"detective_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f442\U0001f3fe", "ear: Medium-Dark Skin Tone", []string{"ear_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f442\U0001f3ff", "ear: Dark Skin Tone", []string{"ear_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f442\U0001f3fb", "ear: Light Skin Tone", []string{"ear_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f442\U0001f3fc", "ear: Medium-Light Skin Tone", []string{"ear_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f442\U0001f3fd", "ear: Medium Skin Tone", []string{"ear_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9bb\U0001f3fe", "ear with hearing aid: Medium-Dark Skin Tone", []string{"ear_with_hearing_aid_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9bb\U0001f3ff", "ear with hearing aid: Dark Skin Tone", []string{"ear_with_hearing_aid_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9bb\U0001f3fb", "ear with hearing aid: Light Skin Tone", []string{"ear_with_hearing_aid_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9bb\U0001f3fc", "ear with hearing aid: Medium-Light Skin Tone", []string{"ear_with_hearing_aid_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9bb\U0001f3fd", "ear with hearing aid: Medium Skin Tone", []string{"ear_with_hearing_aid_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9bb\U0001f3fe", "ear with hearing aid: Medium-Dark Skin Tone", []string{"ear_with_hearing_aid_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9bb\U0001f3ff", "ear with hearing aid: Dark Skin Tone", []string{"ear_with_hearing_aid_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fb", "elf: Light Skin Tone", []string{"elf_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fc", "elf: Medium-Light Skin Tone", []string{"elf_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fd", "elf: Medium Skin Tone", []string{"elf_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fe", "elf: Medium-Dark Skin Tone", []string{"elf_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3ff", "elf: Dark Skin Tone", []string{"elf_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f", "man elf: Dark Skin Tone", []string{"elf_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f", "man elf: Light Skin Tone", []string{"elf_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f", "man elf: Medium-Light Skin Tone", []string{"elf_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f", "man elf: Medium Skin Tone", []string{"elf_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f", "man elf: Medium-Dark Skin Tone", []string{"elf_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f", "man elf: Dark Skin Tone", []string{"elf_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f", "woman elf: Light Skin Tone", []string{"elf_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f", "woman elf: Medium-Light Skin Tone", []string{"elf_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f", "woman elf: Medium Skin Tone", []string{"elf_woman_Medium_Skin_Tone"}, "12.0", false},
@@ -2001,111 +2040,111 @@ var GemojiData = Gemoji{
 	{"\U0001f926\U0001f3fd", "person facepalming: Medium Skin Tone", []string{"facepalm_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fe", "person facepalming: Medium-Dark Skin Tone", []string{"facepalm_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3ff", "person facepalming: Dark Skin Tone", []string{"facepalm_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\U0001f3ed", "factory worker: Dark Skin Tone", []string{"factory_worker_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f3ed", "factory worker: Light Skin Tone", []string{"factory_worker_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f3ed", "factory worker: Medium-Light Skin Tone", []string{"factory_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f3ed", "factory worker: Medium Skin Tone", []string{"factory_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f3ed", "factory worker: Medium-Dark Skin Tone", []string{"factory_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\U0001f3ed", "factory worker: Dark Skin Tone", []string{"factory_worker_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9da\U0001f3fe", "fairy: Medium-Dark Skin Tone", []string{"fairy_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9da\U0001f3ff", "fairy: Dark Skin Tone", []string{"fairy_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fb", "fairy: Light Skin Tone", []string{"fairy_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fc", "fairy: Medium-Light Skin Tone", []string{"fairy_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fd", "fairy: Medium Skin Tone", []string{"fairy_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9da\U0001f3fe", "fairy: Medium-Dark Skin Tone", []string{"fairy_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9da\U0001f3ff", "fairy: Dark Skin Tone", []string{"fairy_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fb\u200d\u2642\ufe0f", "man fairy: Light Skin Tone", []string{"fairy_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fc\u200d\u2642\ufe0f", "man fairy: Medium-Light Skin Tone", []string{"fairy_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fd\u200d\u2642\ufe0f", "man fairy: Medium Skin Tone", []string{"fairy_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fe\u200d\u2642\ufe0f", "man fairy: Medium-Dark Skin Tone", []string{"fairy_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3ff\u200d\u2642\ufe0f", "man fairy: Dark Skin Tone", []string{"fairy_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9da\U0001f3fe\u200d\u2640\ufe0f", "woman fairy: Medium-Dark Skin Tone", []string{"fairy_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3ff\u200d\u2640\ufe0f", "woman fairy: Dark Skin Tone", []string{"fairy_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fb\u200d\u2640\ufe0f", "woman fairy: Light Skin Tone", []string{"fairy_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fc\u200d\u2640\ufe0f", "woman fairy: Medium-Light Skin Tone", []string{"fairy_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9da\U0001f3fd\u200d\u2640\ufe0f", "woman fairy: Medium Skin Tone", []string{"fairy_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f33e", "farmer: Light Skin Tone", []string{"farmer_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f33e", "farmer: Medium-Light Skin Tone", []string{"farmer_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9da\U0001f3fe\u200d\u2640\ufe0f", "woman fairy: Medium-Dark Skin Tone", []string{"fairy_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f33e", "farmer: Medium Skin Tone", []string{"farmer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f33e", "farmer: Medium-Dark Skin Tone", []string{"farmer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f33e", "farmer: Dark Skin Tone", []string{"farmer_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f33e", "farmer: Light Skin Tone", []string{"farmer_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f33e", "farmer: Medium-Light Skin Tone", []string{"farmer_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f575\U0001f3ff\ufe0f\u200d\u2640\ufe0f", "woman detective: Dark Skin Tone", []string{"female_detective_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f575\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman detective: Light Skin Tone", []string{"female_detective_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman detective: Medium-Light Skin Tone", []string{"female_detective_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman detective: Medium Skin Tone", []string{"female_detective_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman detective: Medium-Dark Skin Tone", []string{"female_detective_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f575\U0001f3ff\ufe0f\u200d\u2640\ufe0f", "woman detective: Dark Skin Tone", []string{"female_detective_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f575\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman detective: Light Skin Tone", []string{"female_detective_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f692", "firefighter: Medium-Light Skin Tone", []string{"firefighter_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f692", "firefighter: Medium Skin Tone", []string{"firefighter_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f692", "firefighter: Medium-Dark Skin Tone", []string{"firefighter_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f692", "firefighter: Dark Skin Tone", []string{"firefighter_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f692", "firefighter: Light Skin Tone", []string{"firefighter_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f91b\U0001f3fb", "left-facing fist: Light Skin Tone", []string{"fist_left_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91b\U0001f3fc", "left-facing fist: Medium-Light Skin Tone", []string{"fist_left_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91b\U0001f3fd", "left-facing fist: Medium Skin Tone", []string{"fist_left_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f91b\U0001f3fe", "left-facing fist: Medium-Dark Skin Tone", []string{"fist_left_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f91b\U0001f3ff", "left-facing fist: Dark Skin Tone", []string{"fist_left_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f91b\U0001f3fb", "left-facing fist: Light Skin Tone", []string{"fist_left_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f44a\U0001f3fb", "oncoming fist: Light Skin Tone", []string{"fist_oncoming_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44a\U0001f3fc", "oncoming fist: Medium-Light Skin Tone", []string{"fist_oncoming_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44a\U0001f3fd", "oncoming fist: Medium Skin Tone", []string{"fist_oncoming_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f44a\U0001f3fe", "oncoming fist: Medium-Dark Skin Tone", []string{"fist_oncoming_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f44a\U0001f3ff", "oncoming fist: Dark Skin Tone", []string{"fist_oncoming_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f44a\U0001f3fb", "oncoming fist: Light Skin Tone", []string{"fist_oncoming_Light_Skin_Tone"}, "12.0", false},
+	{"\u270a\U0001f3fb", "raised fist: Light Skin Tone", []string{"fist_raised_Light_Skin_Tone"}, "12.0", false},
 	{"\u270a\U0001f3fc", "raised fist: Medium-Light Skin Tone", []string{"fist_raised_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u270a\U0001f3fd", "raised fist: Medium Skin Tone", []string{"fist_raised_Medium_Skin_Tone"}, "12.0", false},
 	{"\u270a\U0001f3fe", "raised fist: Medium-Dark Skin Tone", []string{"fist_raised_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\u270a\U0001f3ff", "raised fist: Dark Skin Tone", []string{"fist_raised_Dark_Skin_Tone"}, "12.0", false},
-	{"\u270a\U0001f3fb", "raised fist: Light Skin Tone", []string{"fist_raised_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91c\U0001f3fb", "right-facing fist: Light Skin Tone", []string{"fist_right_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91c\U0001f3fc", "right-facing fist: Medium-Light Skin Tone", []string{"fist_right_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91c\U0001f3fd", "right-facing fist: Medium Skin Tone", []string{"fist_right_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f91c\U0001f3fe", "right-facing fist: Medium-Dark Skin Tone", []string{"fist_right_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f91c\U0001f3ff", "right-facing fist: Dark Skin Tone", []string{"fist_right_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9b6\U0001f3fc", "foot: Medium-Light Skin Tone", []string{"foot_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b6\U0001f3fd", "foot: Medium Skin Tone", []string{"foot_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b6\U0001f3fe", "foot: Medium-Dark Skin Tone", []string{"foot_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b6\U0001f3ff", "foot: Dark Skin Tone", []string{"foot_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b6\U0001f3fb", "foot: Light Skin Tone", []string{"foot_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9b6\U0001f3fc", "foot: Medium-Light Skin Tone", []string{"foot_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64d\U0001f3fd\u200d\u2642\ufe0f", "man frowning: Medium Skin Tone", []string{"frowning_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fe\u200d\u2642\ufe0f", "man frowning: Medium-Dark Skin Tone", []string{"frowning_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3ff\u200d\u2642\ufe0f", "man frowning: Dark Skin Tone", []string{"frowning_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fb\u200d\u2642\ufe0f", "man frowning: Light Skin Tone", []string{"frowning_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fc\u200d\u2642\ufe0f", "man frowning: Medium-Light Skin Tone", []string{"frowning_man_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f64d\U0001f3fd\u200d\u2642\ufe0f", "man frowning: Medium Skin Tone", []string{"frowning_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f64d\U0001f3fb", "person frowning: Light Skin Tone", []string{"frowning_person_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64d\U0001f3fc", "person frowning: Medium-Light Skin Tone", []string{"frowning_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fd", "person frowning: Medium Skin Tone", []string{"frowning_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fe", "person frowning: Medium-Dark Skin Tone", []string{"frowning_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3ff", "person frowning: Dark Skin Tone", []string{"frowning_person_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64d\U0001f3fb", "person frowning: Light Skin Tone", []string{"frowning_person_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f64d\U0001f3fc", "person frowning: Medium-Light Skin Tone", []string{"frowning_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fb\u200d\u2640\ufe0f", "woman frowning: Light Skin Tone", []string{"frowning_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fc\u200d\u2640\ufe0f", "woman frowning: Medium-Light Skin Tone", []string{"frowning_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fd\u200d\u2640\ufe0f", "woman frowning: Medium Skin Tone", []string{"frowning_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3fe\u200d\u2640\ufe0f", "woman frowning: Medium-Dark Skin Tone", []string{"frowning_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64d\U0001f3ff\u200d\u2640\ufe0f", "woman frowning: Dark Skin Tone", []string{"frowning_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f467\U0001f3fc", "girl: Medium-Light Skin Tone", []string{"girl_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f467\U0001f3fd", "girl: Medium Skin Tone", []string{"girl_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f467\U0001f3fe", "girl: Medium-Dark Skin Tone", []string{"girl_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f467\U0001f3ff", "girl: Dark Skin Tone", []string{"girl_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f467\U0001f3fb", "girl: Light Skin Tone", []string{"girl_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f467\U0001f3fc", "girl: Medium-Light Skin Tone", []string{"girl_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3cc\U0001f3ff\ufe0f", "person golfing: Dark Skin Tone", []string{"golfing_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fb\ufe0f", "person golfing: Light Skin Tone", []string{"golfing_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fc\ufe0f", "person golfing: Medium-Light Skin Tone", []string{"golfing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fd\ufe0f", "person golfing: Medium Skin Tone", []string{"golfing_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fe\ufe0f", "person golfing: Medium-Dark Skin Tone", []string{"golfing_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3cc\U0001f3ff\ufe0f", "person golfing: Dark Skin Tone", []string{"golfing_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fb\ufe0f\u200d\u2642\ufe0f", "man golfing: Light Skin Tone", []string{"golfing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2642\ufe0f", "man golfing: Medium-Light Skin Tone", []string{"golfing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fd\ufe0f\u200d\u2642\ufe0f", "man golfing: Medium Skin Tone", []string{"golfing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fe\ufe0f\u200d\u2642\ufe0f", "man golfing: Medium-Dark Skin Tone", []string{"golfing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3ff\ufe0f\u200d\u2642\ufe0f", "man golfing: Dark Skin Tone", []string{"golfing_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3cc\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman golfing: Light Skin Tone", []string{"golfing_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Light Skin Tone", []string{"golfing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium Skin Tone", []string{"golfing_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Dark Skin Tone", []string{"golfing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cc\U0001f3ff\ufe0f\u200d\u2640\ufe0f", "woman golfing: Dark Skin Tone", []string{"golfing_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f3cc\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman golfing: Light Skin Tone", []string{"golfing_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Light Skin Tone", []string{"golfing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fb", "guard: Light Skin Tone", []string{"guard_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fc", "guard: Medium-Light Skin Tone", []string{"guard_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fd", "guard: Medium Skin Tone", []string{"guard_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fe", "guard: Medium-Dark Skin Tone", []string{"guard_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3ff", "guard: Dark Skin Tone", []string{"guard_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f482\U0001f3fd\u200d\u2642\ufe0f", "man guard: Medium Skin Tone", []string{"guardsman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f482\U0001f3fe\u200d\u2642\ufe0f", "man guard: Medium-Dark Skin Tone", []string{"guardsman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3ff\u200d\u2642\ufe0f", "man guard: Dark Skin Tone", []string{"guardsman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fb\u200d\u2642\ufe0f", "man guard: Light Skin Tone", []string{"guardsman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fc\u200d\u2642\ufe0f", "man guard: Medium-Light Skin Tone", []string{"guardsman_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f482\U0001f3fd\u200d\u2642\ufe0f", "man guard: Medium Skin Tone", []string{"guardsman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f482\U0001f3fe\u200d\u2642\ufe0f", "man guard: Medium-Dark Skin Tone", []string{"guardsman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fb\u200d\u2640\ufe0f", "woman guard: Light Skin Tone", []string{"guardswoman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fc\u200d\u2640\ufe0f", "woman guard: Medium-Light Skin Tone", []string{"guardswoman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f482\U0001f3fd\u200d\u2640\ufe0f", "woman guard: Medium Skin Tone", []string{"guardswoman_Medium_Skin_Tone"}, "12.0", false},
@@ -2116,131 +2155,131 @@ var GemojiData = Gemoji{
 	{"\U0001f487\U0001f3fb", "person getting haircut: Light Skin Tone", []string{"haircut_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fc", "person getting haircut: Medium-Light Skin Tone", []string{"haircut_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fd", "person getting haircut: Medium Skin Tone", []string{"haircut_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f487\U0001f3fb\u200d\u2642\ufe0f", "man getting haircut: Light Skin Tone", []string{"haircut_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fc\u200d\u2642\ufe0f", "man getting haircut: Medium-Light Skin Tone", []string{"haircut_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fd\u200d\u2642\ufe0f", "man getting haircut: Medium Skin Tone", []string{"haircut_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fe\u200d\u2642\ufe0f", "man getting haircut: Medium-Dark Skin Tone", []string{"haircut_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3ff\u200d\u2642\ufe0f", "man getting haircut: Dark Skin Tone", []string{"haircut_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f487\U0001f3fb\u200d\u2642\ufe0f", "man getting haircut: Light Skin Tone", []string{"haircut_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f487\U0001f3ff\u200d\u2640\ufe0f", "woman getting haircut: Dark Skin Tone", []string{"haircut_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f487\U0001f3fb\u200d\u2640\ufe0f", "woman getting haircut: Light Skin Tone", []string{"haircut_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fc\u200d\u2640\ufe0f", "woman getting haircut: Medium-Light Skin Tone", []string{"haircut_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fd\u200d\u2640\ufe0f", "woman getting haircut: Medium Skin Tone", []string{"haircut_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f487\U0001f3fe\u200d\u2640\ufe0f", "woman getting haircut: Medium-Dark Skin Tone", []string{"haircut_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f487\U0001f3ff\u200d\u2640\ufe0f", "woman getting haircut: Dark Skin Tone", []string{"haircut_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f487\U0001f3fb\u200d\u2640\ufe0f", "woman getting haircut: Light Skin Tone", []string{"haircut_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\u270b\U0001f3ff", "raised hand: Dark Skin Tone", []string{"hand_Dark_Skin_Tone"}, "12.0", false},
+	{"\u270b\U0001f3fb", "raised hand: Light Skin Tone", []string{"hand_Light_Skin_Tone"}, "12.0", false},
 	{"\u270b\U0001f3fc", "raised hand: Medium-Light Skin Tone", []string{"hand_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u270b\U0001f3fd", "raised hand: Medium Skin Tone", []string{"hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\u270b\U0001f3fe", "raised hand: Medium-Dark Skin Tone", []string{"hand_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\u270b\U0001f3ff", "raised hand: Dark Skin Tone", []string{"hand_Dark_Skin_Tone"}, "12.0", false},
-	{"\u270b\U0001f3fb", "raised hand: Light Skin Tone", []string{"hand_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f93e\U0001f3fd", "person playing handball: Medium Skin Tone", []string{"handball_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fe", "person playing handball: Medium-Dark Skin Tone", []string{"handball_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3ff", "person playing handball: Dark Skin Tone", []string{"handball_person_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fb", "person playing handball: Light Skin Tone", []string{"handball_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fc", "person playing handball: Medium-Light Skin Tone", []string{"handball_person_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f93e\U0001f3fd", "person playing handball: Medium Skin Tone", []string{"handball_person_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f", "health worker: Dark Skin Tone", []string{"health_worker_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f", "health worker: Light Skin Tone", []string{"health_worker_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f", "health worker: Medium-Light Skin Tone", []string{"health_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f", "health worker: Medium Skin Tone", []string{"health_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f", "health worker: Medium-Dark Skin Tone", []string{"health_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f", "health worker: Dark Skin Tone", []string{"health_worker_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c7\U0001f3fb", "horse racing: Light Skin Tone", []string{"horse_racing_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c7\U0001f3fc", "horse racing: Medium-Light Skin Tone", []string{"horse_racing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c7\U0001f3fd", "horse racing: Medium Skin Tone", []string{"horse_racing_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c7\U0001f3fe", "horse racing: Medium-Dark Skin Tone", []string{"horse_racing_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c7\U0001f3ff", "horse racing: Dark Skin Tone", []string{"horse_racing_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c7\U0001f3fb", "horse racing: Light Skin Tone", []string{"horse_racing_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c7\U0001f3fc", "horse racing: Medium-Light Skin Tone", []string{"horse_racing_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f", "judge: Dark Skin Tone", []string{"judge_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f", "judge: Light Skin Tone", []string{"judge_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f", "judge: Medium-Light Skin Tone", []string{"judge_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f", "judge: Medium Skin Tone", []string{"judge_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f", "judge: Medium-Dark Skin Tone", []string{"judge_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f", "judge: Dark Skin Tone", []string{"judge_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f", "judge: Light Skin Tone", []string{"judge_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fd", "person juggling: Medium Skin Tone", []string{"juggling_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fe", "person juggling: Medium-Dark Skin Tone", []string{"juggling_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3ff", "person juggling: Dark Skin Tone", []string{"juggling_person_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fb", "person juggling: Light Skin Tone", []string{"juggling_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fc", "person juggling: Medium-Light Skin Tone", []string{"juggling_person_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f", "man kneeling: Light Skin Tone", []string{"kneeling_man_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f", "man kneeling: Medium-Light Skin Tone", []string{"kneeling_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f", "man kneeling: Medium Skin Tone", []string{"kneeling_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f", "man kneeling: Medium-Dark Skin Tone", []string{"kneeling_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f", "man kneeling: Dark Skin Tone", []string{"kneeling_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f", "man kneeling: Light Skin Tone", []string{"kneeling_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f", "man kneeling: Medium-Light Skin Tone", []string{"kneeling_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fb", "person kneeling: Light Skin Tone", []string{"kneeling_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fc", "person kneeling: Medium-Light Skin Tone", []string{"kneeling_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fd", "person kneeling: Medium Skin Tone", []string{"kneeling_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fe", "person kneeling: Medium-Dark Skin Tone", []string{"kneeling_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3ff", "person kneeling: Dark Skin Tone", []string{"kneeling_person_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f", "woman kneeling: Dark Skin Tone", []string{"kneeling_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f", "woman kneeling: Light Skin Tone", []string{"kneeling_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f", "woman kneeling: Medium-Light Skin Tone", []string{"kneeling_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f", "woman kneeling: Medium Skin Tone", []string{"kneeling_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f", "woman kneeling: Medium-Dark Skin Tone", []string{"kneeling_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f", "woman kneeling: Dark Skin Tone", []string{"kneeling_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f", "woman kneeling: Light Skin Tone", []string{"kneeling_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b5\U0001f3fb", "leg: Light Skin Tone", []string{"leg_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b5\U0001f3fc", "leg: Medium-Light Skin Tone", []string{"leg_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b5\U0001f3fd", "leg: Medium Skin Tone", []string{"leg_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b5\U0001f3fe", "leg: Medium-Dark Skin Tone", []string{"leg_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b5\U0001f3ff", "leg: Dark Skin Tone", []string{"leg_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fb", "person in lotus position: Light Skin Tone", []string{"lotus_position_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fc", "person in lotus position: Medium-Light Skin Tone", []string{"lotus_position_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fd", "person in lotus position: Medium Skin Tone", []string{"lotus_position_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fe", "person in lotus position: Medium-Dark Skin Tone", []string{"lotus_position_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3ff", "person in lotus position: Dark Skin Tone", []string{"lotus_position_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fb", "person in lotus position: Light Skin Tone", []string{"lotus_position_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fc", "person in lotus position: Medium-Light Skin Tone", []string{"lotus_position_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f", "man in lotus position: Medium Skin Tone", []string{"lotus_position_man_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f", "man in lotus position: Medium-Dark Skin Tone", []string{"lotus_position_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f", "man in lotus position: Dark Skin Tone", []string{"lotus_position_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f", "man in lotus position: Light Skin Tone", []string{"lotus_position_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f", "man in lotus position: Medium-Light Skin Tone", []string{"lotus_position_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f", "man in lotus position: Medium Skin Tone", []string{"lotus_position_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f", "man in lotus position: Medium-Dark Skin Tone", []string{"lotus_position_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f", "woman in lotus position: Light Skin Tone", []string{"lotus_position_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f", "woman in lotus position: Medium-Light Skin Tone", []string{"lotus_position_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f", "woman in lotus position: Medium Skin Tone", []string{"lotus_position_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f", "woman in lotus position: Medium-Dark Skin Tone", []string{"lotus_position_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f", "woman in lotus position: Dark Skin Tone", []string{"lotus_position_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f", "woman in lotus position: Light Skin Tone", []string{"lotus_position_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f", "woman in lotus position: Medium-Light Skin Tone", []string{"lotus_position_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f91f\U0001f3ff", "love-you gesture: Dark Skin Tone", []string{"love_you_gesture_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f91f\U0001f3fb", "love-you gesture: Light Skin Tone", []string{"love_you_gesture_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91f\U0001f3fc", "love-you gesture: Medium-Light Skin Tone", []string{"love_you_gesture_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91f\U0001f3fd", "love-you gesture: Medium Skin Tone", []string{"love_you_gesture_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f91f\U0001f3fe", "love-you gesture: Medium-Dark Skin Tone", []string{"love_you_gesture_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f91f\U0001f3ff", "love-you gesture: Dark Skin Tone", []string{"love_you_gesture_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f91f\U0001f3fb", "love-you gesture: Light Skin Tone", []string{"love_you_gesture_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fb", "mage: Light Skin Tone", []string{"mage_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fc", "mage: Medium-Light Skin Tone", []string{"mage_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fd", "mage: Medium Skin Tone", []string{"mage_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fe", "mage: Medium-Dark Skin Tone", []string{"mage_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3ff", "mage: Dark Skin Tone", []string{"mage_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f", "man mage: Medium-Dark Skin Tone", []string{"mage_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f", "man mage: Dark Skin Tone", []string{"mage_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f", "man mage: Light Skin Tone", []string{"mage_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f", "man mage: Medium-Light Skin Tone", []string{"mage_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f", "man mage: Medium Skin Tone", []string{"mage_man_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f", "woman mage: Light Skin Tone", []string{"mage_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f", "woman mage: Medium-Light Skin Tone", []string{"mage_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f", "man mage: Medium-Dark Skin Tone", []string{"mage_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f", "man mage: Dark Skin Tone", []string{"mage_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f", "woman mage: Medium Skin Tone", []string{"mage_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f", "woman mage: Medium-Dark Skin Tone", []string{"mage_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f", "woman mage: Dark Skin Tone", []string{"mage_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f", "woman mage: Light Skin Tone", []string{"mage_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f", "woman mage: Medium-Light Skin Tone", []string{"mage_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f575\U0001f3fb\ufe0f\u200d\u2642\ufe0f", "man detective: Light Skin Tone", []string{"male_detective_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fc\ufe0f\u200d\u2642\ufe0f", "man detective: Medium-Light Skin Tone", []string{"male_detective_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fd\ufe0f\u200d\u2642\ufe0f", "man detective: Medium Skin Tone", []string{"male_detective_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3fe\ufe0f\u200d\u2642\ufe0f", "man detective: Medium-Dark Skin Tone", []string{"male_detective_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f575\U0001f3ff\ufe0f\u200d\u2642\ufe0f", "man detective: Dark Skin Tone", []string{"male_detective_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f575\U0001f3fb\ufe0f\u200d\u2642\ufe0f", "man detective: Light Skin Tone", []string{"male_detective_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe", "man: Medium-Dark Skin Tone", []string{"man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff", "man: Dark Skin Tone", []string{"man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb", "man: Light Skin Tone", []string{"man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc", "man: Medium-Light Skin Tone", []string{"man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd", "man: Medium Skin Tone", []string{"man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe", "man: Medium-Dark Skin Tone", []string{"man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff", "man: Dark Skin Tone", []string{"man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fd\u200d\U0001f3a8", "man artist: Medium Skin Tone", []string{"man_artist_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f3a8", "man artist: Medium-Dark Skin Tone", []string{"man_artist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f3a8", "man artist: Dark Skin Tone", []string{"man_artist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f3a8", "man artist: Light Skin Tone", []string{"man_artist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f3a8", "man artist: Medium-Light Skin Tone", []string{"man_artist_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fd\u200d\U0001f3a8", "man artist: Medium Skin Tone", []string{"man_artist_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f3a8", "man artist: Medium-Dark Skin Tone", []string{"man_artist_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\U0001f680", "man astronaut: Medium-Light Skin Tone", []string{"man_astronaut_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f680", "man astronaut: Medium Skin Tone", []string{"man_astronaut_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f680", "man astronaut: Medium-Dark Skin Tone", []string{"man_astronaut_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f680", "man astronaut: Dark Skin Tone", []string{"man_astronaut_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f680", "man astronaut: Light Skin Tone", []string{"man_astronaut_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\U0001f680", "man astronaut: Medium-Light Skin Tone", []string{"man_astronaut_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f938\U0001f3fb\u200d\u2642\ufe0f", "man cartwheeling: Light Skin Tone", []string{"man_cartwheeling_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fc\u200d\u2642\ufe0f", "man cartwheeling: Medium-Light Skin Tone", []string{"man_cartwheeling_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fd\u200d\u2642\ufe0f", "man cartwheeling: Medium Skin Tone", []string{"man_cartwheeling_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fe\u200d\u2642\ufe0f", "man cartwheeling: Medium-Dark Skin Tone", []string{"man_cartwheeling_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3ff\u200d\u2642\ufe0f", "man cartwheeling: Dark Skin Tone", []string{"man_cartwheeling_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f938\U0001f3fb\u200d\u2642\ufe0f", "man cartwheeling: Light Skin Tone", []string{"man_cartwheeling_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f373", "man cook: Medium-Dark Skin Tone", []string{"man_cook_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f373", "man cook: Dark Skin Tone", []string{"man_cook_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f373", "man cook: Light Skin Tone", []string{"man_cook_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f373", "man cook: Medium-Light Skin Tone", []string{"man_cook_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f373", "man cook: Medium Skin Tone", []string{"man_cook_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f373", "man cook: Medium-Dark Skin Tone", []string{"man_cook_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f57a\U0001f3fb", "man dancing: Light Skin Tone", []string{"man_dancing_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f57a\U0001f3fc", "man dancing: Medium-Light Skin Tone", []string{"man_dancing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f57a\U0001f3fd", "man dancing: Medium Skin Tone", []string{"man_dancing_Medium_Skin_Tone"}, "12.0", false},
@@ -2251,61 +2290,61 @@ var GemojiData = Gemoji{
 	{"\U0001f926\U0001f3fb\u200d\u2642\ufe0f", "man facepalming: Light Skin Tone", []string{"man_facepalming_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fc\u200d\u2642\ufe0f", "man facepalming: Medium-Light Skin Tone", []string{"man_facepalming_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fd\u200d\u2642\ufe0f", "man facepalming: Medium Skin Tone", []string{"man_facepalming_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f3ed", "man factory worker: Dark Skin Tone", []string{"man_factory_worker_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f3ed", "man factory worker: Light Skin Tone", []string{"man_factory_worker_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f3ed", "man factory worker: Medium-Light Skin Tone", []string{"man_factory_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f3ed", "man factory worker: Medium Skin Tone", []string{"man_factory_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f3ed", "man factory worker: Medium-Dark Skin Tone", []string{"man_factory_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f3ed", "man factory worker: Dark Skin Tone", []string{"man_factory_worker_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f33e", "man farmer: Light Skin Tone", []string{"man_farmer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f33e", "man farmer: Medium-Light Skin Tone", []string{"man_farmer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f33e", "man farmer: Medium Skin Tone", []string{"man_farmer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f33e", "man farmer: Medium-Dark Skin Tone", []string{"man_farmer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f33e", "man farmer: Dark Skin Tone", []string{"man_farmer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f692", "man firefighter: Dark Skin Tone", []string{"man_firefighter_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f692", "man firefighter: Light Skin Tone", []string{"man_firefighter_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f692", "man firefighter: Medium-Light Skin Tone", []string{"man_firefighter_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f692", "man firefighter: Medium Skin Tone", []string{"man_firefighter_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f692", "man firefighter: Medium-Dark Skin Tone", []string{"man_firefighter_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f692", "man firefighter: Dark Skin Tone", []string{"man_firefighter_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\u2695\ufe0f", "man health worker: Medium-Dark Skin Tone", []string{"man_health_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\u2695\ufe0f", "man health worker: Dark Skin Tone", []string{"man_health_worker_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\u2695\ufe0f", "man health worker: Light Skin Tone", []string{"man_health_worker_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\u2695\ufe0f", "man health worker: Medium-Light Skin Tone", []string{"man_health_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\u2695\ufe0f", "man health worker: Medium Skin Tone", []string{"man_health_worker_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\u2695\ufe0f", "man health worker: Medium-Dark Skin Tone", []string{"man_health_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\u2695\ufe0f", "man health worker: Dark Skin Tone", []string{"man_health_worker_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\U0001f9bd", "man in manual wheelchair: Light Skin Tone", []string{"man_in_manual_wheelchair_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\U0001f9bd", "man in manual wheelchair: Medium-Light Skin Tone", []string{"man_in_manual_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9bd", "man in manual wheelchair: Medium Skin Tone", []string{"man_in_manual_wheelchair_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f9bd", "man in manual wheelchair: Medium-Dark Skin Tone", []string{"man_in_manual_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f9bd", "man in manual wheelchair: Dark Skin Tone", []string{"man_in_manual_wheelchair_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\U0001f9bc", "man in motorized wheelchair: Light Skin Tone", []string{"man_in_motorized_wheelchair_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\U0001f9bc", "man in motorized wheelchair: Medium-Light Skin Tone", []string{"man_in_motorized_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\U0001f9bd", "man in manual wheelchair: Light Skin Tone", []string{"man_in_manual_wheelchair_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\U0001f9bd", "man in manual wheelchair: Medium-Light Skin Tone", []string{"man_in_manual_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9bc", "man in motorized wheelchair: Medium Skin Tone", []string{"man_in_motorized_wheelchair_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f9bc", "man in motorized wheelchair: Medium-Dark Skin Tone", []string{"man_in_motorized_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f9bc", "man in motorized wheelchair: Dark Skin Tone", []string{"man_in_motorized_wheelchair_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\U0001f9bc", "man in motorized wheelchair: Light Skin Tone", []string{"man_in_motorized_wheelchair_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\U0001f9bc", "man in motorized wheelchair: Medium-Light Skin Tone", []string{"man_in_motorized_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\u2696\ufe0f", "man judge: Light Skin Tone", []string{"man_judge_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\u2696\ufe0f", "man judge: Medium-Light Skin Tone", []string{"man_judge_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\u2696\ufe0f", "man judge: Medium Skin Tone", []string{"man_judge_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\u2696\ufe0f", "man judge: Medium-Dark Skin Tone", []string{"man_judge_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\u2696\ufe0f", "man judge: Dark Skin Tone", []string{"man_judge_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\u2696\ufe0f", "man judge: Light Skin Tone", []string{"man_judge_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\u2696\ufe0f", "man judge: Medium-Light Skin Tone", []string{"man_judge_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f939\U0001f3fc\u200d\u2642\ufe0f", "man juggling: Medium-Light Skin Tone", []string{"man_juggling_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f939\U0001f3fd\u200d\u2642\ufe0f", "man juggling: Medium Skin Tone", []string{"man_juggling_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fe\u200d\u2642\ufe0f", "man juggling: Medium-Dark Skin Tone", []string{"man_juggling_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3ff\u200d\u2642\ufe0f", "man juggling: Dark Skin Tone", []string{"man_juggling_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fb\u200d\u2642\ufe0f", "man juggling: Light Skin Tone", []string{"man_juggling_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f939\U0001f3fc\u200d\u2642\ufe0f", "man juggling: Medium-Light Skin Tone", []string{"man_juggling_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f939\U0001f3fd\u200d\u2642\ufe0f", "man juggling: Medium Skin Tone", []string{"man_juggling_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\U0001f527", "man mechanic: Light Skin Tone", []string{"man_mechanic_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\U0001f527", "man mechanic: Medium-Light Skin Tone", []string{"man_mechanic_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f527", "man mechanic: Medium Skin Tone", []string{"man_mechanic_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f527", "man mechanic: Medium-Dark Skin Tone", []string{"man_mechanic_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f527", "man mechanic: Dark Skin Tone", []string{"man_mechanic_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\U0001f527", "man mechanic: Light Skin Tone", []string{"man_mechanic_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\U0001f527", "man mechanic: Medium-Light Skin Tone", []string{"man_mechanic_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f4bc", "man office worker: Light Skin Tone", []string{"man_office_worker_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f4bc", "man office worker: Medium-Light Skin Tone", []string{"man_office_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f4bc", "man office worker: Medium Skin Tone", []string{"man_office_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f4bc", "man office worker: Medium-Dark Skin Tone", []string{"man_office_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f4bc", "man office worker: Dark Skin Tone", []string{"man_office_worker_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\u2708\ufe0f", "man pilot: Light Skin Tone", []string{"man_pilot_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\u2708\ufe0f", "man pilot: Medium-Light Skin Tone", []string{"man_pilot_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\u2708\ufe0f", "man pilot: Medium Skin Tone", []string{"man_pilot_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\u2708\ufe0f", "man pilot: Medium-Dark Skin Tone", []string{"man_pilot_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\u2708\ufe0f", "man pilot: Dark Skin Tone", []string{"man_pilot_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\u2708\ufe0f", "man pilot: Light Skin Tone", []string{"man_pilot_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\u2708\ufe0f", "man pilot: Medium-Light Skin Tone", []string{"man_pilot_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fb\u200d\u2642\ufe0f", "man playing handball: Light Skin Tone", []string{"man_playing_handball_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fc\u200d\u2642\ufe0f", "man playing handball: Medium-Light Skin Tone", []string{"man_playing_handball_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fd\u200d\u2642\ufe0f", "man playing handball: Medium Skin Tone", []string{"man_playing_handball_Medium_Skin_Tone"}, "12.0", false},
@@ -2321,91 +2360,91 @@ var GemojiData = Gemoji{
 	{"\U0001f468\U0001f3fd\u200d\U0001f52c", "man scientist: Medium Skin Tone", []string{"man_scientist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f52c", "man scientist: Medium-Dark Skin Tone", []string{"man_scientist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f52c", "man scientist: Dark Skin Tone", []string{"man_scientist_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f937\U0001f3fe\u200d\u2642\ufe0f", "man shrugging: Medium-Dark Skin Tone", []string{"man_shrugging_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f937\U0001f3ff\u200d\u2642\ufe0f", "man shrugging: Dark Skin Tone", []string{"man_shrugging_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fb\u200d\u2642\ufe0f", "man shrugging: Light Skin Tone", []string{"man_shrugging_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fc\u200d\u2642\ufe0f", "man shrugging: Medium-Light Skin Tone", []string{"man_shrugging_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fd\u200d\u2642\ufe0f", "man shrugging: Medium Skin Tone", []string{"man_shrugging_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f937\U0001f3fe\u200d\u2642\ufe0f", "man shrugging: Medium-Dark Skin Tone", []string{"man_shrugging_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f937\U0001f3ff\u200d\u2642\ufe0f", "man shrugging: Dark Skin Tone", []string{"man_shrugging_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f3a4", "man singer: Light Skin Tone", []string{"man_singer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f3a4", "man singer: Medium-Light Skin Tone", []string{"man_singer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f3a4", "man singer: Medium Skin Tone", []string{"man_singer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f3a4", "man singer: Medium-Dark Skin Tone", []string{"man_singer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f3a4", "man singer: Dark Skin Tone", []string{"man_singer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f393", "man student: Medium-Dark Skin Tone", []string{"man_student_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f393", "man student: Dark Skin Tone", []string{"man_student_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f393", "man student: Light Skin Tone", []string{"man_student_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f393", "man student: Medium-Light Skin Tone", []string{"man_student_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f393", "man student: Medium Skin Tone", []string{"man_student_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f393", "man student: Medium-Dark Skin Tone", []string{"man_student_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f393", "man student: Dark Skin Tone", []string{"man_student_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f3eb", "man teacher: Dark Skin Tone", []string{"man_teacher_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\U0001f3eb", "man teacher: Light Skin Tone", []string{"man_teacher_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f3eb", "man teacher: Medium-Light Skin Tone", []string{"man_teacher_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f3eb", "man teacher: Medium Skin Tone", []string{"man_teacher_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f3eb", "man teacher: Medium-Dark Skin Tone", []string{"man_teacher_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f3eb", "man teacher: Dark Skin Tone", []string{"man_teacher_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\U0001f3eb", "man teacher: Light Skin Tone", []string{"man_teacher_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f4bb", "man technologist: Light Skin Tone", []string{"man_technologist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f4bb", "man technologist: Medium-Light Skin Tone", []string{"man_technologist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f4bb", "man technologist: Medium Skin Tone", []string{"man_technologist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f4bb", "man technologist: Medium-Dark Skin Tone", []string{"man_technologist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f4bb", "man technologist: Dark Skin Tone", []string{"man_technologist_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f472\U0001f3fe", "person with skullcap: Medium-Dark Skin Tone", []string{"man_with_gua_pi_mao_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f472\U0001f3ff", "person with skullcap: Dark Skin Tone", []string{"man_with_gua_pi_mao_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f472\U0001f3fb", "person with skullcap: Light Skin Tone", []string{"man_with_gua_pi_mao_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f472\U0001f3fc", "person with skullcap: Medium-Light Skin Tone", []string{"man_with_gua_pi_mao_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f472\U0001f3fd", "person with skullcap: Medium Skin Tone", []string{"man_with_gua_pi_mao_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f472\U0001f3fe", "person with skullcap: Medium-Dark Skin Tone", []string{"man_with_gua_pi_mao_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f472\U0001f3ff", "person with skullcap: Dark Skin Tone", []string{"man_with_gua_pi_mao_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fe\u200d\U0001f9af", "man with white cane: Medium-Dark Skin Tone", []string{"man_with_probing_cane_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3ff\u200d\U0001f9af", "man with white cane: Dark Skin Tone", []string{"man_with_probing_cane_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f9af", "man with white cane: Light Skin Tone", []string{"man_with_probing_cane_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f9af", "man with white cane: Medium-Light Skin Tone", []string{"man_with_probing_cane_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9af", "man with white cane: Medium Skin Tone", []string{"man_with_probing_cane_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fe\u200d\U0001f9af", "man with white cane: Medium-Dark Skin Tone", []string{"man_with_probing_cane_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3ff\u200d\U0001f9af", "man with white cane: Dark Skin Tone", []string{"man_with_probing_cane_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3fc\u200d\u2642\ufe0f", "man wearing turban: Medium-Light Skin Tone", []string{"man_with_turban_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3fd\u200d\u2642\ufe0f", "man wearing turban: Medium Skin Tone", []string{"man_with_turban_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fe\u200d\u2642\ufe0f", "man wearing turban: Medium-Dark Skin Tone", []string{"man_with_turban_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3ff\u200d\u2642\ufe0f", "man wearing turban: Dark Skin Tone", []string{"man_with_turban_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fb\u200d\u2642\ufe0f", "man wearing turban: Light Skin Tone", []string{"man_with_turban_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3fc\u200d\u2642\ufe0f", "man wearing turban: Medium-Light Skin Tone", []string{"man_with_turban_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3fd\u200d\u2642\ufe0f", "man wearing turban: Medium Skin Tone", []string{"man_with_turban_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fb", "person getting massage: Light Skin Tone", []string{"massage_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fc", "person getting massage: Medium-Light Skin Tone", []string{"massage_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fd", "person getting massage: Medium Skin Tone", []string{"massage_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fe", "person getting massage: Medium-Dark Skin Tone", []string{"massage_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3ff", "person getting massage: Dark Skin Tone", []string{"massage_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f486\U0001f3fe\u200d\u2642\ufe0f", "man getting massage: Medium-Dark Skin Tone", []string{"massage_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3ff\u200d\u2642\ufe0f", "man getting massage: Dark Skin Tone", []string{"massage_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fb\u200d\u2642\ufe0f", "man getting massage: Light Skin Tone", []string{"massage_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fc\u200d\u2642\ufe0f", "man getting massage: Medium-Light Skin Tone", []string{"massage_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fd\u200d\u2642\ufe0f", "man getting massage: Medium Skin Tone", []string{"massage_man_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f486\U0001f3fe\u200d\u2642\ufe0f", "man getting massage: Medium-Dark Skin Tone", []string{"massage_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f486\U0001f3fc\u200d\u2640\ufe0f", "woman getting massage: Medium-Light Skin Tone", []string{"massage_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fd\u200d\u2640\ufe0f", "woman getting massage: Medium Skin Tone", []string{"massage_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fe\u200d\u2640\ufe0f", "woman getting massage: Medium-Dark Skin Tone", []string{"massage_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3ff\u200d\u2640\ufe0f", "woman getting massage: Dark Skin Tone", []string{"massage_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f486\U0001f3fb\u200d\u2640\ufe0f", "woman getting massage: Light Skin Tone", []string{"massage_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f486\U0001f3fc\u200d\u2640\ufe0f", "woman getting massage: Medium-Light Skin Tone", []string{"massage_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fe\u200d\U0001f527", "mechanic: Medium-Dark Skin Tone", []string{"mechanic_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\U0001f527", "mechanic: Dark Skin Tone", []string{"mechanic_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f527", "mechanic: Light Skin Tone", []string{"mechanic_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f527", "mechanic: Medium-Light Skin Tone", []string{"mechanic_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f527", "mechanic: Medium Skin Tone", []string{"mechanic_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fe\u200d\U0001f527", "mechanic: Medium-Dark Skin Tone", []string{"mechanic_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\U0001f527", "mechanic: Dark Skin Tone", []string{"mechanic_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f", "mermaid: Dark Skin Tone", []string{"mermaid_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f", "mermaid: Light Skin Tone", []string{"mermaid_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f", "mermaid: Medium-Light Skin Tone", []string{"mermaid_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f", "mermaid: Medium Skin Tone", []string{"mermaid_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f", "mermaid: Medium-Dark Skin Tone", []string{"mermaid_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f", "mermaid: Dark Skin Tone", []string{"mermaid_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f", "merman: Medium-Dark Skin Tone", []string{"merman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f", "merman: Dark Skin Tone", []string{"merman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f", "merman: Light Skin Tone", []string{"merman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f", "merman: Medium-Light Skin Tone", []string{"merman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f", "merman: Medium Skin Tone", []string{"merman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f", "merman: Medium-Dark Skin Tone", []string{"merman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f", "merman: Dark Skin Tone", []string{"merman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9dc\U0001f3fb", "merperson: Light Skin Tone", []string{"merperson_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fc", "merperson: Medium-Light Skin Tone", []string{"merperson_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fd", "merperson: Medium Skin Tone", []string{"merperson_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3fe", "merperson: Medium-Dark Skin Tone", []string{"merperson_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9dc\U0001f3ff", "merperson: Dark Skin Tone", []string{"merperson_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9dc\U0001f3fb", "merperson: Light Skin Tone", []string{"merperson_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f918\U0001f3fc", "sign of the horns: Medium-Light Skin Tone", []string{"metal_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f918\U0001f3fd", "sign of the horns: Medium Skin Tone", []string{"metal_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f918\U0001f3fe", "sign of the horns: Medium-Dark Skin Tone", []string{"metal_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f918\U0001f3ff", "sign of the horns: Dark Skin Tone", []string{"metal_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f918\U0001f3fb", "sign of the horns: Light Skin Tone", []string{"metal_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f595\U0001f3fb", "middle finger: Light Skin Tone", []string{"middle_finger_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f595\U0001f3fc", "middle finger: Medium-Light Skin Tone", []string{"middle_finger_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f595\U0001f3fd", "middle finger: Medium Skin Tone", []string{"middle_finger_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f595\U0001f3fe", "middle finger: Medium-Dark Skin Tone", []string{"middle_finger_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f595\U0001f3ff", "middle finger: Dark Skin Tone", []string{"middle_finger_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f595\U0001f3fb", "middle finger: Light Skin Tone", []string{"middle_finger_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fe", "person mountain biking: Medium-Dark Skin Tone", []string{"mountain_bicyclist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3ff", "person mountain biking: Dark Skin Tone", []string{"mountain_bicyclist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fb", "person mountain biking: Light Skin Tone", []string{"mountain_bicyclist_Light_Skin_Tone"}, "12.0", false},
@@ -2416,36 +2455,36 @@ var GemojiData = Gemoji{
 	{"\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f", "man mountain biking: Medium Skin Tone", []string{"mountain_biking_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f", "man mountain biking: Medium-Dark Skin Tone", []string{"mountain_biking_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f", "man mountain biking: Dark Skin Tone", []string{"mountain_biking_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f", "woman mountain biking: Medium-Dark Skin Tone", []string{"mountain_biking_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f", "woman mountain biking: Dark Skin Tone", []string{"mountain_biking_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f", "woman mountain biking: Light Skin Tone", []string{"mountain_biking_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f", "woman mountain biking: Medium-Light Skin Tone", []string{"mountain_biking_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f", "woman mountain biking: Medium Skin Tone", []string{"mountain_biking_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f", "woman mountain biking: Medium-Dark Skin Tone", []string{"mountain_biking_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f", "woman mountain biking: Dark Skin Tone", []string{"mountain_biking_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f936\U0001f3fb", "Mrs. Claus: Light Skin Tone", []string{"mrs_claus_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f936\U0001f3fc", "Mrs. Claus: Medium-Light Skin Tone", []string{"mrs_claus_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f936\U0001f3fd", "Mrs. Claus: Medium Skin Tone", []string{"mrs_claus_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f936\U0001f3fe", "Mrs. Claus: Medium-Dark Skin Tone", []string{"mrs_claus_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f936\U0001f3ff", "Mrs. Claus: Dark Skin Tone", []string{"mrs_claus_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f4aa\U0001f3ff", "flexed biceps: Dark Skin Tone", []string{"muscle_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f4aa\U0001f3fb", "flexed biceps: Light Skin Tone", []string{"muscle_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f4aa\U0001f3fc", "flexed biceps: Medium-Light Skin Tone", []string{"muscle_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f4aa\U0001f3fd", "flexed biceps: Medium Skin Tone", []string{"muscle_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f4aa\U0001f3fe", "flexed biceps: Medium-Dark Skin Tone", []string{"muscle_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f485\U0001f3ff", "nail polish: Dark Skin Tone", []string{"nail_care_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f4aa\U0001f3ff", "flexed biceps: Dark Skin Tone", []string{"muscle_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f485\U0001f3fb", "nail polish: Light Skin Tone", []string{"nail_care_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f485\U0001f3fc", "nail polish: Medium-Light Skin Tone", []string{"nail_care_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f485\U0001f3fd", "nail polish: Medium Skin Tone", []string{"nail_care_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f485\U0001f3fe", "nail polish: Medium-Dark Skin Tone", []string{"nail_care_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f485\U0001f3ff", "nail polish: Dark Skin Tone", []string{"nail_care_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f645\U0001f3fb", "person gesturing NO: Light Skin Tone", []string{"no_good_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f645\U0001f3fc", "person gesturing NO: Medium-Light Skin Tone", []string{"no_good_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fd", "person gesturing NO: Medium Skin Tone", []string{"no_good_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fe", "person gesturing NO: Medium-Dark Skin Tone", []string{"no_good_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3ff", "person gesturing NO: Dark Skin Tone", []string{"no_good_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f645\U0001f3fb", "person gesturing NO: Light Skin Tone", []string{"no_good_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f645\U0001f3fc", "person gesturing NO: Medium-Light Skin Tone", []string{"no_good_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f645\U0001f3fc\u200d\u2642\ufe0f", "man gesturing NO: Medium-Light Skin Tone", []string{"no_good_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fd\u200d\u2642\ufe0f", "man gesturing NO: Medium Skin Tone", []string{"no_good_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fe\u200d\u2642\ufe0f", "man gesturing NO: Medium-Dark Skin Tone", []string{"no_good_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3ff\u200d\u2642\ufe0f", "man gesturing NO: Dark Skin Tone", []string{"no_good_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fb\u200d\u2642\ufe0f", "man gesturing NO: Light Skin Tone", []string{"no_good_man_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f645\U0001f3fc\u200d\u2642\ufe0f", "man gesturing NO: Medium-Light Skin Tone", []string{"no_good_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fb\u200d\u2640\ufe0f", "woman gesturing NO: Light Skin Tone", []string{"no_good_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fc\u200d\u2640\ufe0f", "woman gesturing NO: Medium-Light Skin Tone", []string{"no_good_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f645\U0001f3fd\u200d\u2640\ufe0f", "woman gesturing NO: Medium Skin Tone", []string{"no_good_woman_Medium_Skin_Tone"}, "12.0", false},
@@ -2456,31 +2495,31 @@ var GemojiData = Gemoji{
 	{"\U0001f443\U0001f3fd", "nose: Medium Skin Tone", []string{"nose_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f443\U0001f3fe", "nose: Medium-Dark Skin Tone", []string{"nose_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f443\U0001f3ff", "nose: Dark Skin Tone", []string{"nose_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f4bc", "office worker: Light Skin Tone", []string{"office_worker_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f4bc", "office worker: Medium-Light Skin Tone", []string{"office_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f4bc", "office worker: Medium Skin Tone", []string{"office_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f4bc", "office worker: Medium-Dark Skin Tone", []string{"office_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f4bc", "office worker: Dark Skin Tone", []string{"office_worker_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f44c\U0001f3fc", "OK hand: Medium-Light Skin Tone", []string{"ok_hand_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f44c\U0001f3fd", "OK hand: Medium Skin Tone", []string{"ok_hand_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f4bc", "office worker: Light Skin Tone", []string{"office_worker_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f4bc", "office worker: Medium-Light Skin Tone", []string{"office_worker_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44c\U0001f3fe", "OK hand: Medium-Dark Skin Tone", []string{"ok_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f44c\U0001f3ff", "OK hand: Dark Skin Tone", []string{"ok_hand_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f44c\U0001f3fb", "OK hand: Light Skin Tone", []string{"ok_hand_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f44c\U0001f3fc", "OK hand: Medium-Light Skin Tone", []string{"ok_hand_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f44c\U0001f3fd", "OK hand: Medium Skin Tone", []string{"ok_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fb\u200d\u2642\ufe0f", "man gesturing OK: Light Skin Tone", []string{"ok_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fc\u200d\u2642\ufe0f", "man gesturing OK: Medium-Light Skin Tone", []string{"ok_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fd\u200d\u2642\ufe0f", "man gesturing OK: Medium Skin Tone", []string{"ok_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fe\u200d\u2642\ufe0f", "man gesturing OK: Medium-Dark Skin Tone", []string{"ok_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3ff\u200d\u2642\ufe0f", "man gesturing OK: Dark Skin Tone", []string{"ok_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f646\U0001f3ff", "person gesturing OK: Dark Skin Tone", []string{"ok_person_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fb", "person gesturing OK: Light Skin Tone", []string{"ok_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fc", "person gesturing OK: Medium-Light Skin Tone", []string{"ok_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fd", "person gesturing OK: Medium Skin Tone", []string{"ok_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fe", "person gesturing OK: Medium-Dark Skin Tone", []string{"ok_person_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f646\U0001f3ff", "person gesturing OK: Dark Skin Tone", []string{"ok_person_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f646\U0001f3fc\u200d\u2640\ufe0f", "woman gesturing OK: Medium-Light Skin Tone", []string{"ok_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f646\U0001f3fd\u200d\u2640\ufe0f", "woman gesturing OK: Medium Skin Tone", []string{"ok_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fe\u200d\u2640\ufe0f", "woman gesturing OK: Medium-Dark Skin Tone", []string{"ok_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3ff\u200d\u2640\ufe0f", "woman gesturing OK: Dark Skin Tone", []string{"ok_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f646\U0001f3fb\u200d\u2640\ufe0f", "woman gesturing OK: Light Skin Tone", []string{"ok_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f646\U0001f3fc\u200d\u2640\ufe0f", "woman gesturing OK: Medium-Light Skin Tone", []string{"ok_woman_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f646\U0001f3fd\u200d\u2640\ufe0f", "woman gesturing OK: Medium Skin Tone", []string{"ok_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d3\U0001f3fb", "older person: Light Skin Tone", []string{"older_adult_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d3\U0001f3fc", "older person: Medium-Light Skin Tone", []string{"older_adult_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d3\U0001f3fd", "older person: Medium Skin Tone", []string{"older_adult_Medium_Skin_Tone"}, "12.0", false},
@@ -2496,206 +2535,206 @@ var GemojiData = Gemoji{
 	{"\U0001f475\U0001f3fd", "old woman: Medium Skin Tone", []string{"older_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f475\U0001f3fe", "old woman: Medium-Dark Skin Tone", []string{"older_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f475\U0001f3ff", "old woman: Dark Skin Tone", []string{"older_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f450\U0001f3fb", "open hands: Light Skin Tone", []string{"open_hands_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f450\U0001f3fc", "open hands: Medium-Light Skin Tone", []string{"open_hands_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f450\U0001f3fd", "open hands: Medium Skin Tone", []string{"open_hands_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f450\U0001f3fe", "open hands: Medium-Dark Skin Tone", []string{"open_hands_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f450\U0001f3ff", "open hands: Dark Skin Tone", []string{"open_hands_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f450\U0001f3fb", "open hands: Light Skin Tone", []string{"open_hands_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f932\U0001f3fd", "palms up together: Medium Skin Tone", []string{"palms_up_together_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f932\U0001f3fe", "palms up together: Medium-Dark Skin Tone", []string{"palms_up_together_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f932\U0001f3ff", "palms up together: Dark Skin Tone", []string{"palms_up_together_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f932\U0001f3fb", "palms up together: Light Skin Tone", []string{"palms_up_together_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f932\U0001f3fc", "palms up together: Medium-Light Skin Tone", []string{"palms_up_together_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f932\U0001f3fd", "palms up together: Medium Skin Tone", []string{"palms_up_together_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Light Skin Tone", []string{"people_holding_hands_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Light Skin Tone", []string{"people_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium Skin Tone", []string{"people_holding_hands_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Dark Skin Tone", []string{"people_holding_hands_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Dark Skin Tone", []string{"people_holding_hands_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Light Skin Tone", []string{"people_holding_hands_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Light Skin Tone", []string{"people_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fd\u200d\U0001f9b2", "person: bald: Medium Skin Tone", []string{"person_bald_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fe\u200d\U0001f9b2", "person: bald: Medium-Dark Skin Tone", []string{"person_bald_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f9b2", "person: bald: Dark Skin Tone", []string{"person_bald_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b2", "person: bald: Light Skin Tone", []string{"person_bald_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9b2", "person: bald: Medium-Light Skin Tone", []string{"person_bald_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b1", "person: curly hair: Light Skin Tone", []string{"person_curly_hair_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f9b1", "person: curly hair: Medium-Light Skin Tone", []string{"person_curly_hair_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fd\u200d\U0001f9b2", "person: bald: Medium Skin Tone", []string{"person_bald_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fe\u200d\U0001f9b2", "person: bald: Medium-Dark Skin Tone", []string{"person_bald_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9b1", "person: curly hair: Medium Skin Tone", []string{"person_curly_hair_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f9b1", "person: curly hair: Medium-Dark Skin Tone", []string{"person_curly_hair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f9b1", "person: curly hair: Dark Skin Tone", []string{"person_curly_hair_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b1", "person: curly hair: Light Skin Tone", []string{"person_curly_hair_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f9b1", "person: curly hair: Medium-Light Skin Tone", []string{"person_curly_hair_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fe\u200d\U0001f9bd", "person in manual wheelchair: Medium-Dark Skin Tone", []string{"person_in_manual_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\U0001f9bd", "person in manual wheelchair: Dark Skin Tone", []string{"person_in_manual_wheelchair_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f9bd", "person in manual wheelchair: Light Skin Tone", []string{"person_in_manual_wheelchair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9bd", "person in manual wheelchair: Medium-Light Skin Tone", []string{"person_in_manual_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9bd", "person in manual wheelchair: Medium Skin Tone", []string{"person_in_manual_wheelchair_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fe\u200d\U0001f9bd", "person in manual wheelchair: Medium-Dark Skin Tone", []string{"person_in_manual_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\U0001f9bd", "person in manual wheelchair: Dark Skin Tone", []string{"person_in_manual_wheelchair_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fe\u200d\U0001f9bc", "person in motorized wheelchair: Medium-Dark Skin Tone", []string{"person_in_motorized_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f9bc", "person in motorized wheelchair: Dark Skin Tone", []string{"person_in_motorized_wheelchair_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f9bc", "person in motorized wheelchair: Light Skin Tone", []string{"person_in_motorized_wheelchair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9bc", "person in motorized wheelchair: Medium-Light Skin Tone", []string{"person_in_motorized_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9bc", "person in motorized wheelchair: Medium Skin Tone", []string{"person_in_motorized_wheelchair_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fe\u200d\U0001f9bc", "person in motorized wheelchair: Medium-Dark Skin Tone", []string{"person_in_motorized_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f935\U0001f3fb", "person in tuxedo: Light Skin Tone", []string{"person_in_tuxedo_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f935\U0001f3fc", "person in tuxedo: Medium-Light Skin Tone", []string{"person_in_tuxedo_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f935\U0001f3fd", "person in tuxedo: Medium Skin Tone", []string{"person_in_tuxedo_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f935\U0001f3fe", "person in tuxedo: Medium-Dark Skin Tone", []string{"person_in_tuxedo_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f935\U0001f3ff", "person in tuxedo: Dark Skin Tone", []string{"person_in_tuxedo_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3ff\u200d\U0001f9b0", "person: red hair: Dark Skin Tone", []string{"person_red_hair_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b0", "person: red hair: Light Skin Tone", []string{"person_red_hair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9b0", "person: red hair: Medium-Light Skin Tone", []string{"person_red_hair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9b0", "person: red hair: Medium Skin Tone", []string{"person_red_hair_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f9b0", "person: red hair: Medium-Dark Skin Tone", []string{"person_red_hair_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3ff\u200d\U0001f9b0", "person: red hair: Dark Skin Tone", []string{"person_red_hair_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b0", "person: red hair: Light Skin Tone", []string{"person_red_hair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f9b3", "person: white hair: Light Skin Tone", []string{"person_white_hair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9b3", "person: white hair: Medium-Light Skin Tone", []string{"person_white_hair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9b3", "person: white hair: Medium Skin Tone", []string{"person_white_hair_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f9b3", "person: white hair: Medium-Dark Skin Tone", []string{"person_white_hair_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f9b3", "person: white hair: Dark Skin Tone", []string{"person_white_hair_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f9af", "person with white cane: Light Skin Tone", []string{"person_with_probing_cane_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f9af", "person with white cane: Medium-Light Skin Tone", []string{"person_with_probing_cane_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f9af", "person with white cane: Medium Skin Tone", []string{"person_with_probing_cane_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f9af", "person with white cane: Medium-Dark Skin Tone", []string{"person_with_probing_cane_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f9af", "person with white cane: Dark Skin Tone", []string{"person_with_probing_cane_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f9af", "person with white cane: Light Skin Tone", []string{"person_with_probing_cane_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3fe", "person wearing turban: Medium-Dark Skin Tone", []string{"person_with_turban_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3ff", "person wearing turban: Dark Skin Tone", []string{"person_with_turban_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fb", "person wearing turban: Light Skin Tone", []string{"person_with_turban_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fc", "person wearing turban: Medium-Light Skin Tone", []string{"person_with_turban_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fd", "person wearing turban: Medium Skin Tone", []string{"person_with_turban_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3fe", "person wearing turban: Medium-Dark Skin Tone", []string{"person_with_turban_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3ff", "person wearing turban: Dark Skin Tone", []string{"person_with_turban_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f470\U0001f3fc", "person with veil: Medium-Light Skin Tone", []string{"person_with_veil_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f470\U0001f3fd", "person with veil: Medium Skin Tone", []string{"person_with_veil_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f470\U0001f3fe", "person with veil: Medium-Dark Skin Tone", []string{"person_with_veil_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f470\U0001f3ff", "person with veil: Dark Skin Tone", []string{"person_with_veil_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f470\U0001f3fb", "person with veil: Light Skin Tone", []string{"person_with_veil_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f470\U0001f3fc", "person with veil: Medium-Light Skin Tone", []string{"person_with_veil_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f", "pilot: Light Skin Tone", []string{"pilot_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f", "pilot: Medium-Light Skin Tone", []string{"pilot_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f", "pilot: Medium Skin Tone", []string{"pilot_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f", "pilot: Medium-Dark Skin Tone", []string{"pilot_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f", "pilot: Dark Skin Tone", []string{"pilot_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f90f\U0001f3fe", "pinching hand: Medium-Dark Skin Tone", []string{"pinching_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f90f\U0001f3ff", "pinching hand: Dark Skin Tone", []string{"pinching_hand_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f90f\U0001f3fb", "pinching hand: Light Skin Tone", []string{"pinching_hand_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f90f\U0001f3fc", "pinching hand: Medium-Light Skin Tone", []string{"pinching_hand_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f447\U0001f3ff", "backhand index pointing down: Dark Skin Tone", []string{"point_down_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f447\U0001f3fb", "backhand index pointing down: Light Skin Tone", []string{"point_down_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f447\U0001f3fc", "backhand index pointing down: Medium-Light Skin Tone", []string{"point_down_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f447\U0001f3fd", "backhand index pointing down: Medium Skin Tone", []string{"point_down_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f447\U0001f3fe", "backhand index pointing down: Medium-Dark Skin Tone", []string{"point_down_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f448\U0001f3fe", "backhand index pointing left: Medium-Dark Skin Tone", []string{"point_left_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f448\U0001f3ff", "backhand index pointing left: Dark Skin Tone", []string{"point_left_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f448\U0001f3fb", "backhand index pointing left: Light Skin Tone", []string{"point_left_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f448\U0001f3fc", "backhand index pointing left: Medium-Light Skin Tone", []string{"point_left_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f448\U0001f3fd", "backhand index pointing left: Medium Skin Tone", []string{"point_left_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f448\U0001f3fe", "backhand index pointing left: Medium-Dark Skin Tone", []string{"point_left_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f448\U0001f3ff", "backhand index pointing left: Dark Skin Tone", []string{"point_left_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f449\U0001f3fb", "backhand index pointing right: Light Skin Tone", []string{"point_right_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f449\U0001f3fc", "backhand index pointing right: Medium-Light Skin Tone", []string{"point_right_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f449\U0001f3fd", "backhand index pointing right: Medium Skin Tone", []string{"point_right_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f449\U0001f3fe", "backhand index pointing right: Medium-Dark Skin Tone", []string{"point_right_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f449\U0001f3ff", "backhand index pointing right: Dark Skin Tone", []string{"point_right_Dark_Skin_Tone"}, "12.0", false},
-	{"\u261d\U0001f3fb\ufe0f", "index pointing up: Light Skin Tone", []string{"point_up_Light_Skin_Tone"}, "12.0", false},
 	{"\u261d\U0001f3fc\ufe0f", "index pointing up: Medium-Light Skin Tone", []string{"point_up_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u261d\U0001f3fd\ufe0f", "index pointing up: Medium Skin Tone", []string{"point_up_Medium_Skin_Tone"}, "12.0", false},
 	{"\u261d\U0001f3fe\ufe0f", "index pointing up: Medium-Dark Skin Tone", []string{"point_up_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\u261d\U0001f3ff\ufe0f", "index pointing up: Dark Skin Tone", []string{"point_up_Dark_Skin_Tone"}, "12.0", false},
+	{"\u261d\U0001f3fb\ufe0f", "index pointing up: Light Skin Tone", []string{"point_up_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f446\U0001f3fb", "backhand index pointing up: Light Skin Tone", []string{"point_up_2_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f446\U0001f3fc", "backhand index pointing up: Medium-Light Skin Tone", []string{"point_up_2_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f446\U0001f3fd", "backhand index pointing up: Medium Skin Tone", []string{"point_up_2_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f446\U0001f3fe", "backhand index pointing up: Medium-Dark Skin Tone", []string{"point_up_2_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f446\U0001f3ff", "backhand index pointing up: Dark Skin Tone", []string{"point_up_2_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f46e\U0001f3ff", "police officer: Dark Skin Tone", []string{"police_officer_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fb", "police officer: Light Skin Tone", []string{"police_officer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fc", "police officer: Medium-Light Skin Tone", []string{"police_officer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fd", "police officer: Medium Skin Tone", []string{"police_officer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fe", "police officer: Medium-Dark Skin Tone", []string{"police_officer_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f46e\U0001f3ff", "police officer: Dark Skin Tone", []string{"police_officer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f46e\U0001f3fb\u200d\u2642\ufe0f", "man police officer: Light Skin Tone", []string{"policeman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f46e\U0001f3fc\u200d\u2642\ufe0f", "man police officer: Medium-Light Skin Tone", []string{"policeman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fd\u200d\u2642\ufe0f", "man police officer: Medium Skin Tone", []string{"policeman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fe\u200d\u2642\ufe0f", "man police officer: Medium-Dark Skin Tone", []string{"policeman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3ff\u200d\u2642\ufe0f", "man police officer: Dark Skin Tone", []string{"policeman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f46e\U0001f3fb\u200d\u2640\ufe0f", "woman police officer: Light Skin Tone", []string{"policewoman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f46e\U0001f3fc\u200d\u2640\ufe0f", "woman police officer: Medium-Light Skin Tone", []string{"policewoman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f46e\U0001f3fb\u200d\u2642\ufe0f", "man police officer: Light Skin Tone", []string{"policeman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f46e\U0001f3fc\u200d\u2642\ufe0f", "man police officer: Medium-Light Skin Tone", []string{"policeman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fd\u200d\u2640\ufe0f", "woman police officer: Medium Skin Tone", []string{"policewoman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3fe\u200d\u2640\ufe0f", "woman police officer: Medium-Dark Skin Tone", []string{"policewoman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f46e\U0001f3ff\u200d\u2640\ufe0f", "woman police officer: Dark Skin Tone", []string{"policewoman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f46e\U0001f3fb\u200d\u2640\ufe0f", "woman police officer: Light Skin Tone", []string{"policewoman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f46e\U0001f3fc\u200d\u2640\ufe0f", "woman police officer: Medium-Light Skin Tone", []string{"policewoman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64e\U0001f3fb", "person pouting: Light Skin Tone", []string{"pouting_face_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fc", "person pouting: Medium-Light Skin Tone", []string{"pouting_face_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fd", "person pouting: Medium Skin Tone", []string{"pouting_face_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fe", "person pouting: Medium-Dark Skin Tone", []string{"pouting_face_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3ff", "person pouting: Dark Skin Tone", []string{"pouting_face_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64e\U0001f3fb", "person pouting: Light Skin Tone", []string{"pouting_face_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64e\U0001f3fe\u200d\u2642\ufe0f", "man pouting: Medium-Dark Skin Tone", []string{"pouting_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f64e\U0001f3ff\u200d\u2642\ufe0f", "man pouting: Dark Skin Tone", []string{"pouting_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fb\u200d\u2642\ufe0f", "man pouting: Light Skin Tone", []string{"pouting_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fc\u200d\u2642\ufe0f", "man pouting: Medium-Light Skin Tone", []string{"pouting_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fd\u200d\u2642\ufe0f", "man pouting: Medium Skin Tone", []string{"pouting_man_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f64e\U0001f3fe\u200d\u2642\ufe0f", "man pouting: Medium-Dark Skin Tone", []string{"pouting_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64e\U0001f3ff\u200d\u2642\ufe0f", "man pouting: Dark Skin Tone", []string{"pouting_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f64e\U0001f3fb\u200d\u2640\ufe0f", "woman pouting: Light Skin Tone", []string{"pouting_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64e\U0001f3fc\u200d\u2640\ufe0f", "woman pouting: Medium-Light Skin Tone", []string{"pouting_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fd\u200d\u2640\ufe0f", "woman pouting: Medium Skin Tone", []string{"pouting_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3fe\u200d\u2640\ufe0f", "woman pouting: Medium-Dark Skin Tone", []string{"pouting_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64e\U0001f3ff\u200d\u2640\ufe0f", "woman pouting: Dark Skin Tone", []string{"pouting_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64e\U0001f3fb\u200d\u2640\ufe0f", "woman pouting: Light Skin Tone", []string{"pouting_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f64e\U0001f3fc\u200d\u2640\ufe0f", "woman pouting: Medium-Light Skin Tone", []string{"pouting_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64f\U0001f3fb", "folded hands: Light Skin Tone", []string{"pray_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64f\U0001f3fc", "folded hands: Medium-Light Skin Tone", []string{"pray_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64f\U0001f3fd", "folded hands: Medium Skin Tone", []string{"pray_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64f\U0001f3fe", "folded hands: Medium-Dark Skin Tone", []string{"pray_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64f\U0001f3ff", "folded hands: Dark Skin Tone", []string{"pray_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f930\U0001f3fb", "pregnant woman: Light Skin Tone", []string{"pregnant_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f930\U0001f3fc", "pregnant woman: Medium-Light Skin Tone", []string{"pregnant_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f930\U0001f3fd", "pregnant woman: Medium Skin Tone", []string{"pregnant_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f930\U0001f3fe", "pregnant woman: Medium-Dark Skin Tone", []string{"pregnant_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f930\U0001f3ff", "pregnant woman: Dark Skin Tone", []string{"pregnant_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f930\U0001f3fb", "pregnant woman: Light Skin Tone", []string{"pregnant_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f930\U0001f3fc", "pregnant woman: Medium-Light Skin Tone", []string{"pregnant_woman_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f934\U0001f3fe", "prince: Medium-Dark Skin Tone", []string{"prince_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f934\U0001f3ff", "prince: Dark Skin Tone", []string{"prince_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f934\U0001f3fb", "prince: Light Skin Tone", []string{"prince_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f934\U0001f3fc", "prince: Medium-Light Skin Tone", []string{"prince_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f934\U0001f3fd", "prince: Medium Skin Tone", []string{"prince_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f478\U0001f3fb", "princess: Light Skin Tone", []string{"princess_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f934\U0001f3fe", "prince: Medium-Dark Skin Tone", []string{"prince_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f478\U0001f3fc", "princess: Medium-Light Skin Tone", []string{"princess_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f478\U0001f3fd", "princess: Medium Skin Tone", []string{"princess_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f478\U0001f3fe", "princess: Medium-Dark Skin Tone", []string{"princess_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f478\U0001f3ff", "princess: Dark Skin Tone", []string{"princess_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f478\U0001f3fb", "princess: Light Skin Tone", []string{"princess_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f91a\U0001f3ff", "raised back of hand: Dark Skin Tone", []string{"raised_back_of_hand_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f91a\U0001f3fb", "raised back of hand: Light Skin Tone", []string{"raised_back_of_hand_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91a\U0001f3fc", "raised back of hand: Medium-Light Skin Tone", []string{"raised_back_of_hand_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f91a\U0001f3fd", "raised back of hand: Medium Skin Tone", []string{"raised_back_of_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f91a\U0001f3fe", "raised back of hand: Medium-Dark Skin Tone", []string{"raised_back_of_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f91a\U0001f3ff", "raised back of hand: Dark Skin Tone", []string{"raised_back_of_hand_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f590\U0001f3fd\ufe0f", "hand with fingers splayed: Medium Skin Tone", []string{"raised_hand_with_fingers_splayed_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f590\U0001f3fe\ufe0f", "hand with fingers splayed: Medium-Dark Skin Tone", []string{"raised_hand_with_fingers_splayed_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f590\U0001f3ff\ufe0f", "hand with fingers splayed: Dark Skin Tone", []string{"raised_hand_with_fingers_splayed_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f590\U0001f3fb\ufe0f", "hand with fingers splayed: Light Skin Tone", []string{"raised_hand_with_fingers_splayed_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f590\U0001f3fc\ufe0f", "hand with fingers splayed: Medium-Light Skin Tone", []string{"raised_hand_with_fingers_splayed_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f590\U0001f3fd\ufe0f", "hand with fingers splayed: Medium Skin Tone", []string{"raised_hand_with_fingers_splayed_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64c\U0001f3fb", "raising hands: Light Skin Tone", []string{"raised_hands_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64c\U0001f3fc", "raising hands: Medium-Light Skin Tone", []string{"raised_hands_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64c\U0001f3fd", "raising hands: Medium Skin Tone", []string{"raised_hands_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64c\U0001f3fe", "raising hands: Medium-Dark Skin Tone", []string{"raised_hands_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64c\U0001f3ff", "raising hands: Dark Skin Tone", []string{"raised_hands_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f64b\U0001f3ff", "person raising hand: Dark Skin Tone", []string{"raising_hand_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f64b\U0001f3fb", "person raising hand: Light Skin Tone", []string{"raising_hand_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fc", "person raising hand: Medium-Light Skin Tone", []string{"raising_hand_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fd", "person raising hand: Medium Skin Tone", []string{"raising_hand_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fe", "person raising hand: Medium-Dark Skin Tone", []string{"raising_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64b\U0001f3ff", "person raising hand: Dark Skin Tone", []string{"raising_hand_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64b\U0001f3fb", "person raising hand: Light Skin Tone", []string{"raising_hand_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3ff\u200d\u2642\ufe0f", "man raising hand: Dark Skin Tone", []string{"raising_hand_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fb\u200d\u2642\ufe0f", "man raising hand: Light Skin Tone", []string{"raising_hand_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fc\u200d\u2642\ufe0f", "man raising hand: Medium-Light Skin Tone", []string{"raising_hand_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fd\u200d\u2642\ufe0f", "man raising hand: Medium Skin Tone", []string{"raising_hand_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fe\u200d\u2642\ufe0f", "man raising hand: Medium-Dark Skin Tone", []string{"raising_hand_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64b\U0001f3fe\u200d\u2640\ufe0f", "woman raising hand: Medium-Dark Skin Tone", []string{"raising_hand_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f64b\U0001f3ff\u200d\u2640\ufe0f", "woman raising hand: Dark Skin Tone", []string{"raising_hand_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fb\u200d\u2640\ufe0f", "woman raising hand: Light Skin Tone", []string{"raising_hand_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fc\u200d\u2640\ufe0f", "woman raising hand: Medium-Light Skin Tone", []string{"raising_hand_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f64b\U0001f3fd\u200d\u2640\ufe0f", "woman raising hand: Medium Skin Tone", []string{"raising_hand_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fc\u200d\U0001f9b0", "man: red hair: Medium-Light Skin Tone", []string{"red_haired_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f64b\U0001f3fe\u200d\u2640\ufe0f", "woman raising hand: Medium-Dark Skin Tone", []string{"raising_hand_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f64b\U0001f3ff\u200d\u2640\ufe0f", "woman raising hand: Dark Skin Tone", []string{"raising_hand_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9b0", "man: red hair: Medium Skin Tone", []string{"red_haired_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f9b0", "man: red hair: Medium-Dark Skin Tone", []string{"red_haired_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f9b0", "man: red hair: Dark Skin Tone", []string{"red_haired_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fb\u200d\U0001f9b0", "man: red hair: Light Skin Tone", []string{"red_haired_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fc\u200d\U0001f9b0", "man: red hair: Medium-Light Skin Tone", []string{"red_haired_man_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\U0001f9b0", "woman: red hair: Medium-Dark Skin Tone", []string{"red_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f9b0", "woman: red hair: Dark Skin Tone", []string{"red_haired_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9b0", "woman: red hair: Light Skin Tone", []string{"red_haired_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9b0", "woman: red hair: Medium-Light Skin Tone", []string{"red_haired_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9b0", "woman: red hair: Medium Skin Tone", []string{"red_haired_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\U0001f9b0", "woman: red hair: Medium-Dark Skin Tone", []string{"red_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f9b0", "woman: red hair: Dark Skin Tone", []string{"red_haired_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fb", "person rowing boat: Light Skin Tone", []string{"rowboat_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fc", "person rowing boat: Medium-Light Skin Tone", []string{"rowboat_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fd", "person rowing boat: Medium Skin Tone", []string{"rowboat_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fe", "person rowing boat: Medium-Dark Skin Tone", []string{"rowboat_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3ff", "person rowing boat: Dark Skin Tone", []string{"rowboat_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f", "man rowing boat: Dark Skin Tone", []string{"rowing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f", "man rowing boat: Light Skin Tone", []string{"rowing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f", "man rowing boat: Medium-Light Skin Tone", []string{"rowing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f", "man rowing boat: Medium Skin Tone", []string{"rowing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f", "man rowing boat: Medium-Dark Skin Tone", []string{"rowing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f", "man rowing boat: Dark Skin Tone", []string{"rowing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f", "woman rowing boat: Light Skin Tone", []string{"rowing_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f", "woman rowing boat: Medium-Light Skin Tone", []string{"rowing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f", "woman rowing boat: Medium Skin Tone", []string{"rowing_woman_Medium_Skin_Tone"}, "12.0", false},
@@ -2711,41 +2750,41 @@ var GemojiData = Gemoji{
 	{"\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f", "man running: Medium Skin Tone", []string{"running_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f", "man running: Medium-Dark Skin Tone", []string{"running_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f", "man running: Dark Skin Tone", []string{"running_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f", "woman running: Dark Skin Tone", []string{"running_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f", "woman running: Light Skin Tone", []string{"running_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f", "woman running: Medium-Light Skin Tone", []string{"running_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f", "woman running: Medium Skin Tone", []string{"running_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f", "woman running: Medium-Dark Skin Tone", []string{"running_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f", "woman running: Dark Skin Tone", []string{"running_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f385\U0001f3fb", "Santa Claus: Light Skin Tone", []string{"santa_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f385\U0001f3fc", "Santa Claus: Medium-Light Skin Tone", []string{"santa_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f385\U0001f3fd", "Santa Claus: Medium Skin Tone", []string{"santa_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f385\U0001f3fe", "Santa Claus: Medium-Dark Skin Tone", []string{"santa_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f385\U0001f3ff", "Santa Claus: Dark Skin Tone", []string{"santa_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f385\U0001f3fb", "Santa Claus: Light Skin Tone", []string{"santa_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f385\U0001f3fc", "Santa Claus: Medium-Light Skin Tone", []string{"santa_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f", "man in steamy room: Light Skin Tone", []string{"sauna_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f", "man in steamy room: Medium-Light Skin Tone", []string{"sauna_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f", "man in steamy room: Medium Skin Tone", []string{"sauna_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f", "man in steamy room: Medium-Dark Skin Tone", []string{"sauna_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f", "man in steamy room: Dark Skin Tone", []string{"sauna_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d6\U0001f3ff", "person in steamy room: Dark Skin Tone", []string{"sauna_person_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fb", "person in steamy room: Light Skin Tone", []string{"sauna_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fc", "person in steamy room: Medium-Light Skin Tone", []string{"sauna_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fd", "person in steamy room: Medium Skin Tone", []string{"sauna_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fe", "person in steamy room: Medium-Dark Skin Tone", []string{"sauna_person_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d6\U0001f3ff", "person in steamy room: Dark Skin Tone", []string{"sauna_person_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f", "woman in steamy room: Light Skin Tone", []string{"sauna_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f", "woman in steamy room: Medium-Light Skin Tone", []string{"sauna_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f", "woman in steamy room: Medium Skin Tone", []string{"sauna_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f", "woman in steamy room: Medium-Dark Skin Tone", []string{"sauna_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f", "woman in steamy room: Dark Skin Tone", []string{"sauna_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f", "woman in steamy room: Light Skin Tone", []string{"sauna_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f52c", "scientist: Light Skin Tone", []string{"scientist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f52c", "scientist: Medium-Light Skin Tone", []string{"scientist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f52c", "scientist: Medium Skin Tone", []string{"scientist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f52c", "scientist: Medium-Dark Skin Tone", []string{"scientist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f52c", "scientist: Dark Skin Tone", []string{"scientist_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f52c", "scientist: Light Skin Tone", []string{"scientist_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f933\U0001f3fb", "selfie: Light Skin Tone", []string{"selfie_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f933\U0001f3fc", "selfie: Medium-Light Skin Tone", []string{"selfie_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f933\U0001f3fd", "selfie: Medium Skin Tone", []string{"selfie_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f933\U0001f3fe", "selfie: Medium-Dark Skin Tone", []string{"selfie_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f933\U0001f3ff", "selfie: Dark Skin Tone", []string{"selfie_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f933\U0001f3fb", "selfie: Light Skin Tone", []string{"selfie_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fe", "person shrugging: Medium-Dark Skin Tone", []string{"shrug_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3ff", "person shrugging: Dark Skin Tone", []string{"shrug_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fb", "person shrugging: Light Skin Tone", []string{"shrug_Light_Skin_Tone"}, "12.0", false},
@@ -2756,36 +2795,36 @@ var GemojiData = Gemoji{
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f3a4", "singer: Medium Skin Tone", []string{"singer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f3a4", "singer: Medium-Dark Skin Tone", []string{"singer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f3a4", "singer: Dark Skin Tone", []string{"singer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6cc\U0001f3fc", "person in bed: Medium-Light Skin Tone", []string{"sleeping_bed_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6cc\U0001f3fd", "person in bed: Medium Skin Tone", []string{"sleeping_bed_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6cc\U0001f3fe", "person in bed: Medium-Dark Skin Tone", []string{"sleeping_bed_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6cc\U0001f3ff", "person in bed: Dark Skin Tone", []string{"sleeping_bed_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6cc\U0001f3fb", "person in bed: Light Skin Tone", []string{"sleeping_bed_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f6cc\U0001f3fc", "person in bed: Medium-Light Skin Tone", []string{"sleeping_bed_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c2\U0001f3fe", "snowboarder: Medium-Dark Skin Tone", []string{"snowboarder_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c2\U0001f3ff", "snowboarder: Dark Skin Tone", []string{"snowboarder_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c2\U0001f3fb", "snowboarder: Light Skin Tone", []string{"snowboarder_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c2\U0001f3fc", "snowboarder: Medium-Light Skin Tone", []string{"snowboarder_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c2\U0001f3fd", "snowboarder: Medium Skin Tone", []string{"snowboarder_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c2\U0001f3fe", "snowboarder: Medium-Dark Skin Tone", []string{"snowboarder_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f", "man standing: Dark Skin Tone", []string{"standing_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f", "man standing: Light Skin Tone", []string{"standing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f", "man standing: Medium-Light Skin Tone", []string{"standing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f", "man standing: Medium Skin Tone", []string{"standing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f", "man standing: Medium-Dark Skin Tone", []string{"standing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f", "man standing: Dark Skin Tone", []string{"standing_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cd\U0001f3fb", "person standing: Light Skin Tone", []string{"standing_person_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cd\U0001f3fc", "person standing: Medium-Light Skin Tone", []string{"standing_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fd", "person standing: Medium Skin Tone", []string{"standing_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fe", "person standing: Medium-Dark Skin Tone", []string{"standing_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3ff", "person standing: Dark Skin Tone", []string{"standing_person_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cd\U0001f3fb", "person standing: Light Skin Tone", []string{"standing_person_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cd\U0001f3fc", "person standing: Medium-Light Skin Tone", []string{"standing_person_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f", "woman standing: Dark Skin Tone", []string{"standing_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f", "woman standing: Light Skin Tone", []string{"standing_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f", "woman standing: Medium-Light Skin Tone", []string{"standing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f", "woman standing: Medium Skin Tone", []string{"standing_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f", "woman standing: Medium-Dark Skin Tone", []string{"standing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f", "woman standing: Dark Skin Tone", []string{"standing_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f393", "student: Light Skin Tone", []string{"student_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fc\u200d\U0001f393", "student: Medium-Light Skin Tone", []string{"student_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f393", "student: Medium Skin Tone", []string{"student_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f393", "student: Medium-Dark Skin Tone", []string{"student_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f393", "student: Dark Skin Tone", []string{"student_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f393", "student: Light Skin Tone", []string{"student_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fc\u200d\U0001f393", "student: Medium-Light Skin Tone", []string{"student_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fb", "superhero: Light Skin Tone", []string{"superhero_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fc", "superhero: Medium-Light Skin Tone", []string{"superhero_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fd", "superhero: Medium Skin Tone", []string{"superhero_Medium_Skin_Tone"}, "12.0", false},
@@ -2796,81 +2835,81 @@ var GemojiData = Gemoji{
 	{"\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f", "man superhero: Medium Skin Tone", []string{"superhero_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f", "man superhero: Medium-Dark Skin Tone", []string{"superhero_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f", "man superhero: Dark Skin Tone", []string{"superhero_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f", "woman superhero: Dark Skin Tone", []string{"superhero_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f", "woman superhero: Light Skin Tone", []string{"superhero_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f", "woman superhero: Medium-Light Skin Tone", []string{"superhero_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f", "woman superhero: Medium Skin Tone", []string{"superhero_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f", "woman superhero: Medium-Dark Skin Tone", []string{"superhero_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f", "woman superhero: Dark Skin Tone", []string{"superhero_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fe", "supervillain: Medium-Dark Skin Tone", []string{"supervillain_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3ff", "supervillain: Dark Skin Tone", []string{"supervillain_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fb", "supervillain: Light Skin Tone", []string{"supervillain_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fc", "supervillain: Medium-Light Skin Tone", []string{"supervillain_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fd", "supervillain: Medium Skin Tone", []string{"supervillain_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f", "man supervillain: Dark Skin Tone", []string{"supervillain_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f", "man supervillain: Light Skin Tone", []string{"supervillain_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f", "man supervillain: Medium-Light Skin Tone", []string{"supervillain_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f", "man supervillain: Medium Skin Tone", []string{"supervillain_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f", "man supervillain: Medium-Dark Skin Tone", []string{"supervillain_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f", "man supervillain: Dark Skin Tone", []string{"supervillain_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f", "woman supervillain: Light Skin Tone", []string{"supervillain_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f", "woman supervillain: Medium-Light Skin Tone", []string{"supervillain_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f", "woman supervillain: Medium Skin Tone", []string{"supervillain_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f", "woman supervillain: Medium-Dark Skin Tone", []string{"supervillain_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f", "woman supervillain: Dark Skin Tone", []string{"supervillain_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f", "woman supervillain: Light Skin Tone", []string{"supervillain_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f", "woman supervillain: Medium-Light Skin Tone", []string{"supervillain_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c4\U0001f3fb", "person surfing: Light Skin Tone", []string{"surfer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fc", "person surfing: Medium-Light Skin Tone", []string{"surfer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fd", "person surfing: Medium Skin Tone", []string{"surfer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fe", "person surfing: Medium-Dark Skin Tone", []string{"surfer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3ff", "person surfing: Dark Skin Tone", []string{"surfer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c4\U0001f3fb", "person surfing: Light Skin Tone", []string{"surfer_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f", "man surfing: Light Skin Tone", []string{"surfing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f", "man surfing: Medium-Light Skin Tone", []string{"surfing_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f", "man surfing: Medium Skin Tone", []string{"surfing_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f", "man surfing: Medium-Dark Skin Tone", []string{"surfing_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f", "man surfing: Dark Skin Tone", []string{"surfing_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f", "man surfing: Light Skin Tone", []string{"surfing_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f", "woman surfing: Light Skin Tone", []string{"surfing_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f", "woman surfing: Medium-Light Skin Tone", []string{"surfing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f", "woman surfing: Medium Skin Tone", []string{"surfing_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f", "woman surfing: Medium-Dark Skin Tone", []string{"surfing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f", "woman surfing: Dark Skin Tone", []string{"surfing_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3ca\U0001f3ff", "person swimming: Dark Skin Tone", []string{"swimmer_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fb", "person swimming: Light Skin Tone", []string{"swimmer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fc", "person swimming: Medium-Light Skin Tone", []string{"swimmer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fd", "person swimming: Medium Skin Tone", []string{"swimmer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fe", "person swimming: Medium-Dark Skin Tone", []string{"swimmer_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f3ca\U0001f3ff", "person swimming: Dark Skin Tone", []string{"swimmer_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f", "man swimming: Light Skin Tone", []string{"swimming_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f", "man swimming: Medium-Light Skin Tone", []string{"swimming_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f", "man swimming: Medium Skin Tone", []string{"swimming_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f", "man swimming: Medium-Dark Skin Tone", []string{"swimming_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f", "man swimming: Dark Skin Tone", []string{"swimming_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f", "woman swimming: Medium-Light Skin Tone", []string{"swimming_woman_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f", "woman swimming: Medium Skin Tone", []string{"swimming_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f", "woman swimming: Medium-Dark Skin Tone", []string{"swimming_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f", "woman swimming: Dark Skin Tone", []string{"swimming_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f", "woman swimming: Light Skin Tone", []string{"swimming_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f", "woman swimming: Medium-Light Skin Tone", []string{"swimming_woman_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f", "woman swimming: Medium Skin Tone", []string{"swimming_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d1\U0001f3fb\u200d\U0001f3eb", "teacher: Light Skin Tone", []string{"teacher_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f3eb", "teacher: Medium-Light Skin Tone", []string{"teacher_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f3eb", "teacher: Medium Skin Tone", []string{"teacher_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f3eb", "teacher: Medium-Dark Skin Tone", []string{"teacher_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f3eb", "teacher: Dark Skin Tone", []string{"teacher_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d1\U0001f3fb\u200d\U0001f3eb", "teacher: Light Skin Tone", []string{"teacher_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fb\u200d\U0001f4bb", "technologist: Light Skin Tone", []string{"technologist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fc\u200d\U0001f4bb", "technologist: Medium-Light Skin Tone", []string{"technologist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fd\u200d\U0001f4bb", "technologist: Medium Skin Tone", []string{"technologist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3fe\u200d\U0001f4bb", "technologist: Medium-Dark Skin Tone", []string{"technologist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d1\U0001f3ff\u200d\U0001f4bb", "technologist: Dark Skin Tone", []string{"technologist_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f481\U0001f3ff\u200d\u2642\ufe0f", "man tipping hand: Dark Skin Tone", []string{"tipping_hand_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fb\u200d\u2642\ufe0f", "man tipping hand: Light Skin Tone", []string{"tipping_hand_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fc\u200d\u2642\ufe0f", "man tipping hand: Medium-Light Skin Tone", []string{"tipping_hand_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fd\u200d\u2642\ufe0f", "man tipping hand: Medium Skin Tone", []string{"tipping_hand_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fe\u200d\u2642\ufe0f", "man tipping hand: Medium-Dark Skin Tone", []string{"tipping_hand_man_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f481\U0001f3ff\u200d\u2642\ufe0f", "man tipping hand: Dark Skin Tone", []string{"tipping_hand_man_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fb", "person tipping hand: Light Skin Tone", []string{"tipping_hand_person_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fc", "person tipping hand: Medium-Light Skin Tone", []string{"tipping_hand_person_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fd", "person tipping hand: Medium Skin Tone", []string{"tipping_hand_person_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fe", "person tipping hand: Medium-Dark Skin Tone", []string{"tipping_hand_person_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3ff", "person tipping hand: Dark Skin Tone", []string{"tipping_hand_person_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f481\U0001f3fb\u200d\u2640\ufe0f", "woman tipping hand: Light Skin Tone", []string{"tipping_hand_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f481\U0001f3fc\u200d\u2640\ufe0f", "woman tipping hand: Medium-Light Skin Tone", []string{"tipping_hand_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fd\u200d\u2640\ufe0f", "woman tipping hand: Medium Skin Tone", []string{"tipping_hand_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3fe\u200d\u2640\ufe0f", "woman tipping hand: Medium-Dark Skin Tone", []string{"tipping_hand_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f481\U0001f3ff\u200d\u2640\ufe0f", "woman tipping hand: Dark Skin Tone", []string{"tipping_hand_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f481\U0001f3fb\u200d\u2640\ufe0f", "woman tipping hand: Light Skin Tone", []string{"tipping_hand_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f481\U0001f3fc\u200d\u2640\ufe0f", "woman tipping hand: Medium-Light Skin Tone", []string{"tipping_hand_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46c\U0001f3fb", "men holding hands: Light Skin Tone", []string{"two_men_holding_hands_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46c\U0001f3fc", "men holding hands: Medium-Light Skin Tone", []string{"two_men_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f46c\U0001f3fd", "men holding hands: Medium Skin Tone", []string{"two_men_holding_hands_Medium_Skin_Tone"}, "12.0", false},
@@ -2886,21 +2925,21 @@ var GemojiData = Gemoji{
 	{"\u270c\U0001f3fd\ufe0f", "victory hand: Medium Skin Tone", []string{"v_Medium_Skin_Tone"}, "12.0", false},
 	{"\u270c\U0001f3fe\ufe0f", "victory hand: Medium-Dark Skin Tone", []string{"v_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\u270c\U0001f3ff\ufe0f", "victory hand: Dark Skin Tone", []string{"v_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9db\U0001f3fb", "vampire: Light Skin Tone", []string{"vampire_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9db\U0001f3fc", "vampire: Medium-Light Skin Tone", []string{"vampire_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fd", "vampire: Medium Skin Tone", []string{"vampire_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fe", "vampire: Medium-Dark Skin Tone", []string{"vampire_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3ff", "vampire: Dark Skin Tone", []string{"vampire_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9db\U0001f3fb", "vampire: Light Skin Tone", []string{"vampire_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9db\U0001f3fc", "vampire: Medium-Light Skin Tone", []string{"vampire_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fb\u200d\u2642\ufe0f", "man vampire: Light Skin Tone", []string{"vampire_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fc\u200d\u2642\ufe0f", "man vampire: Medium-Light Skin Tone", []string{"vampire_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fd\u200d\u2642\ufe0f", "man vampire: Medium Skin Tone", []string{"vampire_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fe\u200d\u2642\ufe0f", "man vampire: Medium-Dark Skin Tone", []string{"vampire_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3ff\u200d\u2642\ufe0f", "man vampire: Dark Skin Tone", []string{"vampire_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f9db\U0001f3fb\u200d\u2640\ufe0f", "woman vampire: Light Skin Tone", []string{"vampire_woman_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f9db\U0001f3fc\u200d\u2640\ufe0f", "woman vampire: Medium-Light Skin Tone", []string{"vampire_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fd\u200d\u2640\ufe0f", "woman vampire: Medium Skin Tone", []string{"vampire_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3fe\u200d\u2640\ufe0f", "woman vampire: Medium-Dark Skin Tone", []string{"vampire_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9db\U0001f3ff\u200d\u2640\ufe0f", "woman vampire: Dark Skin Tone", []string{"vampire_woman_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9db\U0001f3fb\u200d\u2640\ufe0f", "woman vampire: Light Skin Tone", []string{"vampire_woman_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f9db\U0001f3fc\u200d\u2640\ufe0f", "woman vampire: Medium-Light Skin Tone", []string{"vampire_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f596\U0001f3fb", "vulcan salute: Light Skin Tone", []string{"vulcan_salute_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f596\U0001f3fc", "vulcan salute: Medium-Light Skin Tone", []string{"vulcan_salute_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f596\U0001f3fd", "vulcan salute: Medium Skin Tone", []string{"vulcan_salute_Medium_Skin_Tone"}, "12.0", false},
@@ -2911,26 +2950,26 @@ var GemojiData = Gemoji{
 	{"\U0001f6b6\U0001f3fd", "person walking: Medium Skin Tone", []string{"walking_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fe", "person walking: Medium-Dark Skin Tone", []string{"walking_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3ff", "person walking: Dark Skin Tone", []string{"walking_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f", "man walking: Light Skin Tone", []string{"walking_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f", "man walking: Medium-Light Skin Tone", []string{"walking_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f", "man walking: Medium Skin Tone", []string{"walking_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f", "man walking: Medium-Dark Skin Tone", []string{"walking_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f", "man walking: Dark Skin Tone", []string{"walking_man_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f", "man walking: Light Skin Tone", []string{"walking_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f", "woman walking: Medium-Dark Skin Tone", []string{"walking_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f", "woman walking: Dark Skin Tone", []string{"walking_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f", "woman walking: Light Skin Tone", []string{"walking_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f", "woman walking: Medium-Light Skin Tone", []string{"walking_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f", "woman walking: Medium Skin Tone", []string{"walking_woman_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f", "woman walking: Medium-Dark Skin Tone", []string{"walking_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f", "woman walking: Dark Skin Tone", []string{"walking_woman_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f93d\U0001f3fb", "person playing water polo: Light Skin Tone", []string{"water_polo_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f93d\U0001f3fc", "person playing water polo: Medium-Light Skin Tone", []string{"water_polo_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3fd", "person playing water polo: Medium Skin Tone", []string{"water_polo_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3fe", "person playing water polo: Medium-Dark Skin Tone", []string{"water_polo_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3ff", "person playing water polo: Dark Skin Tone", []string{"water_polo_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f93d\U0001f3fb", "person playing water polo: Light Skin Tone", []string{"water_polo_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f93d\U0001f3fc", "person playing water polo: Medium-Light Skin Tone", []string{"water_polo_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f44b\U0001f3ff", "waving hand: Dark Skin Tone", []string{"wave_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f44b\U0001f3fb", "waving hand: Light Skin Tone", []string{"wave_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44b\U0001f3fc", "waving hand: Medium-Light Skin Tone", []string{"wave_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f44b\U0001f3fd", "waving hand: Medium Skin Tone", []string{"wave_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f44b\U0001f3fe", "waving hand: Medium-Dark Skin Tone", []string{"wave_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f44b\U0001f3ff", "waving hand: Dark Skin Tone", []string{"wave_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f44b\U0001f3fb", "waving hand: Light Skin Tone", []string{"wave_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fb\ufe0f", "person lifting weights: Light Skin Tone", []string{"weight_lifting_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fc\ufe0f", "person lifting weights: Medium-Light Skin Tone", []string{"weight_lifting_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fd\ufe0f", "person lifting weights: Medium Skin Tone", []string{"weight_lifting_Medium_Skin_Tone"}, "12.0", false},
@@ -2941,21 +2980,21 @@ var GemojiData = Gemoji{
 	{"\U0001f3cb\U0001f3fd\ufe0f\u200d\u2642\ufe0f", "man lifting weights: Medium Skin Tone", []string{"weight_lifting_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fe\ufe0f\u200d\u2642\ufe0f", "man lifting weights: Medium-Dark Skin Tone", []string{"weight_lifting_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3ff\ufe0f\u200d\u2642\ufe0f", "man lifting weights: Dark Skin Tone", []string{"weight_lifting_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f3cb\U0001f3ff\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Dark Skin Tone", []string{"weight_lifting_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fb\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Light Skin Tone", []string{"weight_lifting_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Medium-Light Skin Tone", []string{"weight_lifting_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Medium Skin Tone", []string{"weight_lifting_woman_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f3cb\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Medium-Dark Skin Tone", []string{"weight_lifting_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f468\U0001f3fb\u200d\U0001f9b3", "man: white hair: Light Skin Tone", []string{"white_haired_man_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f3cb\U0001f3ff\ufe0f\u200d\u2640\ufe0f", "woman lifting weights: Dark Skin Tone", []string{"weight_lifting_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fc\u200d\U0001f9b3", "man: white hair: Medium-Light Skin Tone", []string{"white_haired_man_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fd\u200d\U0001f9b3", "man: white hair: Medium Skin Tone", []string{"white_haired_man_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3fe\u200d\U0001f9b3", "man: white hair: Medium-Dark Skin Tone", []string{"white_haired_man_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f468\U0001f3ff\u200d\U0001f9b3", "man: white hair: Dark Skin Tone", []string{"white_haired_man_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\U0001f9b3", "woman: white hair: Medium-Dark Skin Tone", []string{"white_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f468\U0001f3fb\u200d\U0001f9b3", "man: white hair: Light Skin Tone", []string{"white_haired_man_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f9b3", "woman: white hair: Dark Skin Tone", []string{"white_haired_woman_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9b3", "woman: white hair: Light Skin Tone", []string{"white_haired_woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9b3", "woman: white hair: Medium-Light Skin Tone", []string{"white_haired_woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9b3", "woman: white hair: Medium Skin Tone", []string{"white_haired_woman_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\U0001f9b3", "woman: white hair: Medium-Dark Skin Tone", []string{"white_haired_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb", "woman: Light Skin Tone", []string{"woman_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc", "woman: Medium-Light Skin Tone", []string{"woman_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd", "woman: Medium Skin Tone", []string{"woman_Medium_Skin_Tone"}, "12.0", false},
@@ -2966,26 +3005,26 @@ var GemojiData = Gemoji{
 	{"\U0001f469\U0001f3fd\u200d\U0001f3a8", "woman artist: Medium Skin Tone", []string{"woman_artist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f3a8", "woman artist: Medium-Dark Skin Tone", []string{"woman_artist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f3a8", "woman artist: Dark Skin Tone", []string{"woman_artist_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f680", "woman astronaut: Dark Skin Tone", []string{"woman_astronaut_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f680", "woman astronaut: Light Skin Tone", []string{"woman_astronaut_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f680", "woman astronaut: Medium-Light Skin Tone", []string{"woman_astronaut_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f680", "woman astronaut: Medium Skin Tone", []string{"woman_astronaut_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f680", "woman astronaut: Medium-Dark Skin Tone", []string{"woman_astronaut_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f680", "woman astronaut: Dark Skin Tone", []string{"woman_astronaut_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fb\u200d\u2640\ufe0f", "woman cartwheeling: Light Skin Tone", []string{"woman_cartwheeling_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fc\u200d\u2640\ufe0f", "woman cartwheeling: Medium-Light Skin Tone", []string{"woman_cartwheeling_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fd\u200d\u2640\ufe0f", "woman cartwheeling: Medium Skin Tone", []string{"woman_cartwheeling_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3fe\u200d\u2640\ufe0f", "woman cartwheeling: Medium-Dark Skin Tone", []string{"woman_cartwheeling_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f938\U0001f3ff\u200d\u2640\ufe0f", "woman cartwheeling: Dark Skin Tone", []string{"woman_cartwheeling_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\U0001f373", "woman cook: Light Skin Tone", []string{"woman_cook_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f373", "woman cook: Medium-Light Skin Tone", []string{"woman_cook_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f373", "woman cook: Medium Skin Tone", []string{"woman_cook_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f373", "woman cook: Medium-Dark Skin Tone", []string{"woman_cook_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f373", "woman cook: Dark Skin Tone", []string{"woman_cook_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\U0001f373", "woman cook: Light Skin Tone", []string{"woman_cook_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f483\U0001f3fb", "woman dancing: Light Skin Tone", []string{"woman_dancing_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f483\U0001f3fc", "woman dancing: Medium-Light Skin Tone", []string{"woman_dancing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f483\U0001f3fd", "woman dancing: Medium Skin Tone", []string{"woman_dancing_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f483\U0001f3fe", "woman dancing: Medium-Dark Skin Tone", []string{"woman_dancing_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f483\U0001f3ff", "woman dancing: Dark Skin Tone", []string{"woman_dancing_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f483\U0001f3fb", "woman dancing: Light Skin Tone", []string{"woman_dancing_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f483\U0001f3fc", "woman dancing: Medium-Light Skin Tone", []string{"woman_dancing_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fb\u200d\u2640\ufe0f", "woman facepalming: Light Skin Tone", []string{"woman_facepalming_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fc\u200d\u2640\ufe0f", "woman facepalming: Medium-Light Skin Tone", []string{"woman_facepalming_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f926\U0001f3fd\u200d\u2640\ufe0f", "woman facepalming: Medium Skin Tone", []string{"woman_facepalming_Medium_Skin_Tone"}, "12.0", false},
@@ -3011,21 +3050,21 @@ var GemojiData = Gemoji{
 	{"\U0001f469\U0001f3fd\u200d\u2695\ufe0f", "woman health worker: Medium Skin Tone", []string{"woman_health_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\u2695\ufe0f", "woman health worker: Medium-Dark Skin Tone", []string{"woman_health_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\u2695\ufe0f", "woman health worker: Dark Skin Tone", []string{"woman_health_worker_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\U0001f9bd", "woman in manual wheelchair: Medium-Dark Skin Tone", []string{"woman_in_manual_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f9bd", "woman in manual wheelchair: Dark Skin Tone", []string{"woman_in_manual_wheelchair_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9bd", "woman in manual wheelchair: Light Skin Tone", []string{"woman_in_manual_wheelchair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9bd", "woman in manual wheelchair: Medium-Light Skin Tone", []string{"woman_in_manual_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9bd", "woman in manual wheelchair: Medium Skin Tone", []string{"woman_in_manual_wheelchair_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\U0001f9bd", "woman in manual wheelchair: Medium-Dark Skin Tone", []string{"woman_in_manual_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f9bd", "woman in manual wheelchair: Dark Skin Tone", []string{"woman_in_manual_wheelchair_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\U0001f9bc", "woman in motorized wheelchair: Medium-Dark Skin Tone", []string{"woman_in_motorized_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f9bc", "woman in motorized wheelchair: Dark Skin Tone", []string{"woman_in_motorized_wheelchair_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9bc", "woman in motorized wheelchair: Light Skin Tone", []string{"woman_in_motorized_wheelchair_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f9bc", "woman in motorized wheelchair: Medium-Light Skin Tone", []string{"woman_in_motorized_wheelchair_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f9bc", "woman in motorized wheelchair: Medium Skin Tone", []string{"woman_in_motorized_wheelchair_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\u2696\ufe0f", "woman judge: Light Skin Tone", []string{"woman_judge_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\U0001f9bc", "woman in motorized wheelchair: Medium-Dark Skin Tone", []string{"woman_in_motorized_wheelchair_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f9bc", "woman in motorized wheelchair: Dark Skin Tone", []string{"woman_in_motorized_wheelchair_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\u2696\ufe0f", "woman judge: Medium-Light Skin Tone", []string{"woman_judge_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\u2696\ufe0f", "woman judge: Medium Skin Tone", []string{"woman_judge_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\u2696\ufe0f", "woman judge: Medium-Dark Skin Tone", []string{"woman_judge_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\u2696\ufe0f", "woman judge: Dark Skin Tone", []string{"woman_judge_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\u2696\ufe0f", "woman judge: Light Skin Tone", []string{"woman_judge_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fb\u200d\u2640\ufe0f", "woman juggling: Light Skin Tone", []string{"woman_juggling_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fc\u200d\u2640\ufe0f", "woman juggling: Medium-Light Skin Tone", []string{"woman_juggling_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f939\U0001f3fd\u200d\u2640\ufe0f", "woman juggling: Medium Skin Tone", []string{"woman_juggling_Medium_Skin_Tone"}, "12.0", false},
@@ -3041,66 +3080,66 @@ var GemojiData = Gemoji{
 	{"\U0001f469\U0001f3fd\u200d\U0001f4bc", "woman office worker: Medium Skin Tone", []string{"woman_office_worker_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f4bc", "woman office worker: Medium-Dark Skin Tone", []string{"woman_office_worker_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f4bc", "woman office worker: Dark Skin Tone", []string{"woman_office_worker_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\u2708\ufe0f", "woman pilot: Medium Skin Tone", []string{"woman_pilot_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\u2708\ufe0f", "woman pilot: Medium-Dark Skin Tone", []string{"woman_pilot_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\u2708\ufe0f", "woman pilot: Dark Skin Tone", []string{"woman_pilot_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\u2708\ufe0f", "woman pilot: Light Skin Tone", []string{"woman_pilot_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\u2708\ufe0f", "woman pilot: Medium-Light Skin Tone", []string{"woman_pilot_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fd\u200d\u2708\ufe0f", "woman pilot: Medium Skin Tone", []string{"woman_pilot_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\u2708\ufe0f", "woman pilot: Medium-Dark Skin Tone", []string{"woman_pilot_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fb\u200d\u2640\ufe0f", "woman playing handball: Light Skin Tone", []string{"woman_playing_handball_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fc\u200d\u2640\ufe0f", "woman playing handball: Medium-Light Skin Tone", []string{"woman_playing_handball_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fd\u200d\u2640\ufe0f", "woman playing handball: Medium Skin Tone", []string{"woman_playing_handball_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3fe\u200d\u2640\ufe0f", "woman playing handball: Medium-Dark Skin Tone", []string{"woman_playing_handball_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93e\U0001f3ff\u200d\u2640\ufe0f", "woman playing handball: Dark Skin Tone", []string{"woman_playing_handball_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f93d\U0001f3fb\u200d\u2640\ufe0f", "woman playing water polo: Light Skin Tone", []string{"woman_playing_water_polo_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3fc\u200d\u2640\ufe0f", "woman playing water polo: Medium-Light Skin Tone", []string{"woman_playing_water_polo_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3fd\u200d\u2640\ufe0f", "woman playing water polo: Medium Skin Tone", []string{"woman_playing_water_polo_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3fe\u200d\u2640\ufe0f", "woman playing water polo: Medium-Dark Skin Tone", []string{"woman_playing_water_polo_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f93d\U0001f3ff\u200d\u2640\ufe0f", "woman playing water polo: Dark Skin Tone", []string{"woman_playing_water_polo_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f93d\U0001f3fb\u200d\u2640\ufe0f", "woman playing water polo: Light Skin Tone", []string{"woman_playing_water_polo_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f52c", "woman scientist: Light Skin Tone", []string{"woman_scientist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f52c", "woman scientist: Medium-Light Skin Tone", []string{"woman_scientist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f52c", "woman scientist: Medium Skin Tone", []string{"woman_scientist_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f52c", "woman scientist: Medium-Dark Skin Tone", []string{"woman_scientist_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f52c", "woman scientist: Dark Skin Tone", []string{"woman_scientist_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f937\U0001f3ff\u200d\u2640\ufe0f", "woman shrugging: Dark Skin Tone", []string{"woman_shrugging_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fb\u200d\u2640\ufe0f", "woman shrugging: Light Skin Tone", []string{"woman_shrugging_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fc\u200d\u2640\ufe0f", "woman shrugging: Medium-Light Skin Tone", []string{"woman_shrugging_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fd\u200d\u2640\ufe0f", "woman shrugging: Medium Skin Tone", []string{"woman_shrugging_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f937\U0001f3fe\u200d\u2640\ufe0f", "woman shrugging: Medium-Dark Skin Tone", []string{"woman_shrugging_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f937\U0001f3ff\u200d\u2640\ufe0f", "woman shrugging: Dark Skin Tone", []string{"woman_shrugging_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\U0001f3a4", "woman singer: Light Skin Tone", []string{"woman_singer_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f3a4", "woman singer: Medium-Light Skin Tone", []string{"woman_singer_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f3a4", "woman singer: Medium Skin Tone", []string{"woman_singer_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f3a4", "woman singer: Medium-Dark Skin Tone", []string{"woman_singer_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f3a4", "woman singer: Dark Skin Tone", []string{"woman_singer_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\U0001f3a4", "woman singer: Light Skin Tone", []string{"woman_singer_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\U0001f393", "woman student: Light Skin Tone", []string{"woman_student_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f393", "woman student: Medium-Light Skin Tone", []string{"woman_student_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f393", "woman student: Medium Skin Tone", []string{"woman_student_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f393", "woman student: Medium-Dark Skin Tone", []string{"woman_student_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f393", "woman student: Dark Skin Tone", []string{"woman_student_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\U0001f393", "woman student: Light Skin Tone", []string{"woman_student_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f3eb", "woman teacher: Dark Skin Tone", []string{"woman_teacher_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fb\u200d\U0001f3eb", "woman teacher: Light Skin Tone", []string{"woman_teacher_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f3eb", "woman teacher: Medium-Light Skin Tone", []string{"woman_teacher_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f3eb", "woman teacher: Medium Skin Tone", []string{"woman_teacher_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f3eb", "woman teacher: Medium-Dark Skin Tone", []string{"woman_teacher_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f3eb", "woman teacher: Dark Skin Tone", []string{"woman_teacher_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fb\u200d\U0001f3eb", "woman teacher: Light Skin Tone", []string{"woman_teacher_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fe\u200d\U0001f4bb", "woman technologist: Medium-Dark Skin Tone", []string{"woman_technologist_Medium-Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3ff\u200d\U0001f4bb", "woman technologist: Dark Skin Tone", []string{"woman_technologist_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f4bb", "woman technologist: Light Skin Tone", []string{"woman_technologist_Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fc\u200d\U0001f4bb", "woman technologist: Medium-Light Skin Tone", []string{"woman_technologist_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fd\u200d\U0001f4bb", "woman technologist: Medium Skin Tone", []string{"woman_technologist_Medium_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fe\u200d\U0001f4bb", "woman technologist: Medium-Dark Skin Tone", []string{"woman_technologist_Medium-Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3ff\u200d\U0001f4bb", "woman technologist: Dark Skin Tone", []string{"woman_technologist_Dark_Skin_Tone"}, "12.0", false},
-	{"\U0001f9d5\U0001f3fc", "woman with headscarf: Medium-Light Skin Tone", []string{"woman_with_headscarf_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d5\U0001f3fd", "woman with headscarf: Medium Skin Tone", []string{"woman_with_headscarf_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d5\U0001f3fe", "woman with headscarf: Medium-Dark Skin Tone", []string{"woman_with_headscarf_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d5\U0001f3ff", "woman with headscarf: Dark Skin Tone", []string{"woman_with_headscarf_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f9d5\U0001f3fb", "woman with headscarf: Light Skin Tone", []string{"woman_with_headscarf_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fc\u200d\U0001f9af", "woman with white cane: Medium-Light Skin Tone", []string{"woman_with_probing_cane_Medium-Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f469\U0001f3fd\u200d\U0001f9af", "woman with white cane: Medium Skin Tone", []string{"woman_with_probing_cane_Medium_Skin_Tone"}, "12.0", false},
+	{"\U0001f9d5\U0001f3fc", "woman with headscarf: Medium-Light Skin Tone", []string{"woman_with_headscarf_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fe\u200d\U0001f9af", "woman with white cane: Medium-Dark Skin Tone", []string{"woman_with_probing_cane_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3ff\u200d\U0001f9af", "woman with white cane: Dark Skin Tone", []string{"woman_with_probing_cane_Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f469\U0001f3fb\u200d\U0001f9af", "woman with white cane: Light Skin Tone", []string{"woman_with_probing_cane_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3fb\u200d\u2640\ufe0f", "woman wearing turban: Light Skin Tone", []string{"woman_with_turban_Light_Skin_Tone"}, "12.0", false},
-	{"\U0001f473\U0001f3fc\u200d\u2640\ufe0f", "woman wearing turban: Medium-Light Skin Tone", []string{"woman_with_turban_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fc\u200d\U0001f9af", "woman with white cane: Medium-Light Skin Tone", []string{"woman_with_probing_cane_Medium-Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f469\U0001f3fd\u200d\U0001f9af", "woman with white cane: Medium Skin Tone", []string{"woman_with_probing_cane_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fd\u200d\u2640\ufe0f", "woman wearing turban: Medium Skin Tone", []string{"woman_with_turban_Medium_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3fe\u200d\u2640\ufe0f", "woman wearing turban: Medium-Dark Skin Tone", []string{"woman_with_turban_Medium-Dark_Skin_Tone"}, "12.0", false},
 	{"\U0001f473\U0001f3ff\u200d\u2640\ufe0f", "woman wearing turban: Dark Skin Tone", []string{"woman_with_turban_Dark_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3fb\u200d\u2640\ufe0f", "woman wearing turban: Light Skin Tone", []string{"woman_with_turban_Light_Skin_Tone"}, "12.0", false},
+	{"\U0001f473\U0001f3fc\u200d\u2640\ufe0f", "woman wearing turban: Medium-Light Skin Tone", []string{"woman_with_turban_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u270d\U0001f3fb\ufe0f", "writing hand: Light Skin Tone", []string{"writing_hand_Light_Skin_Tone"}, "12.0", false},
 	{"\u270d\U0001f3fc\ufe0f", "writing hand: Medium-Light Skin Tone", []string{"writing_hand_Medium-Light_Skin_Tone"}, "12.0", false},
 	{"\u270d\U0001f3fd\ufe0f", "writing hand: Medium Skin Tone", []string{"writing_hand_Medium_Skin_Tone"}, "12.0", false},
diff --git a/modules/eventsource/event.go b/modules/eventsource/event.go
index 9fe20715e3bd0..281a1bb135791 100644
--- a/modules/eventsource/event.go
+++ b/modules/eventsource/event.go
@@ -18,7 +18,7 @@ func wrapNewlines(w io.Writer, prefix, value []byte) (sum int64, err error) {
 	if len(value) == 0 {
 		return
 	}
-	n := 0
+	var n int
 	last := 0
 	for j := bytes.IndexByte(value, '\n'); j > -1; j = bytes.IndexByte(value[last:], '\n') {
 		n, err = w.Write(prefix)
@@ -45,7 +45,7 @@ func wrapNewlines(w io.Writer, prefix, value []byte) (sum int64, err error) {
 	}
 	n, err = w.Write([]byte("\n"))
 	sum += int64(n)
-	return
+	return sum, err
 }
 
 // Event is an eventsource event, not all fields need to be set
@@ -64,7 +64,7 @@ type Event struct {
 // The return value n is the number of bytes written. Any error encountered during the write is also returned.
 func (e *Event) WriteTo(w io.Writer) (int64, error) {
 	sum := int64(0)
-	nint := 0
+	var nint int
 	n, err := wrapNewlines(w, []byte("event: "), []byte(e.Name))
 	sum += n
 	if err != nil {
diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go
index 06d48454fdee4..44e878fd4ea68 100644
--- a/modules/eventsource/manager_run.go
+++ b/modules/eventsource/manager_run.go
@@ -8,7 +8,7 @@ import (
 	"context"
 	"time"
 
-	"code.gitea.io/gitea/models"
+	activities_model "code.gitea.io/gitea/models/activities"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/modules/convert"
 	"code.gitea.io/gitea/modules/graceful"
@@ -72,7 +72,7 @@ loop:
 
 			now := timeutil.TimeStampNow().Add(-2)
 
-			uidCounts, err := models.GetUIDsAndNotificationCounts(then, now)
+			uidCounts, err := activities_model.GetUIDsAndNotificationCounts(then, now)
 			if err != nil {
 				log.Error("Unable to get UIDcounts: %v", err)
 			}
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 902fa897185f5..feb0dd31be184 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -176,12 +176,12 @@ func ReadBatchLine(rd *bufio.Reader) (sha []byte, typ string, size int64, err er
 	typ = typ[:idx]
 
 	size, err = strconv.ParseInt(sizeStr, 10, 64)
-	return
+	return sha, typ, size, err
 }
 
 // ReadTagObjectID reads a tag object ID hash from a cat-file --batch stream, throwing away the rest of the stream.
 func ReadTagObjectID(rd *bufio.Reader, size int64) (string, error) {
-	id := ""
+	var id string
 	var n int64
 headerLoop:
 	for {
@@ -216,7 +216,7 @@ headerLoop:
 
 // ReadTreeID reads a tree ID from a cat-file --batch stream, throwing away the rest of the stream.
 func ReadTreeID(rd *bufio.Reader, size int64) (string, error) {
-	id := ""
+	var id string
 	var n int64
 headerLoop:
 	for {
@@ -328,7 +328,7 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
 	// Deal with the 20-byte SHA
 	idx = 0
 	for idx < 20 {
-		read := 0
+		var read int
 		read, err = rd.Read(shaBuf[idx:20])
 		n += read
 		if err != nil {
@@ -337,7 +337,7 @@ func ParseTreeLine(rd *bufio.Reader, modeBuf, fnameBuf, shaBuf []byte) (mode, fn
 		idx += read
 	}
 	sha = shaBuf
-	return
+	return mode, fname, sha, n, err
 }
 
 var callerPrefix string
diff --git a/modules/git/blame.go b/modules/git/blame.go
index 1653ecbf854af..832b12213c889 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -106,7 +106,7 @@ func (r *BlameReader) Close() error {
 	_ = r.output.Close()
 
 	if err := r.cmd.Wait(); err != nil {
-		return fmt.Errorf("Wait: %v", err)
+		return fmt.Errorf("Wait: %w", err)
 	}
 
 	return nil
@@ -129,13 +129,13 @@ func createBlameReader(ctx context.Context, dir string, command ...string) (*Bla
 	stdout, err := cmd.StdoutPipe()
 	if err != nil {
 		defer finished()
-		return nil, fmt.Errorf("StdoutPipe: %v", err)
+		return nil, fmt.Errorf("StdoutPipe: %w", err)
 	}
 
 	if err = cmd.Start(); err != nil {
 		defer finished()
 		_ = stdout.Close()
-		return nil, fmt.Errorf("Start: %v", err)
+		return nil, fmt.Errorf("Start: %w", err)
 	}
 
 	reader := bufio.NewReader(stdout)
diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go
index 211c188559726..89bb98162f402 100644
--- a/modules/git/blob_nogogit.go
+++ b/modules/git/blob_nogogit.go
@@ -99,7 +99,7 @@ func (b *blobReader) Read(p []byte) (n int, err error) {
 	}
 	n, err = b.rd.Read(p)
 	b.n -= int64(n)
-	return
+	return n, err
 }
 
 // Close implements io.Closer
diff --git a/modules/git/command.go b/modules/git/command.go
index d71497f1d791e..0d94494f11bbc 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -24,7 +24,7 @@ import (
 
 var (
 	// globalCommandArgs global command args for external package setting
-	globalCommandArgs []string
+	globalCommandArgs []CmdArg
 
 	// defaultCommandExecutionTimeout default command execution timeout duration
 	defaultCommandExecutionTimeout = 360 * time.Second
@@ -40,8 +40,11 @@ type Command struct {
 	parentContext    context.Context
 	desc             string
 	globalArgsLength int
+	brokenArgs       []string
 }
 
+type CmdArg string
+
 func (c *Command) String() string {
 	if len(c.args) == 0 {
 		return c.name
@@ -50,28 +53,40 @@ func (c *Command) String() string {
 }
 
 // NewCommand creates and returns a new Git Command based on given command and arguments.
-func NewCommand(ctx context.Context, args ...string) *Command {
+// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
+func NewCommand(ctx context.Context, args ...CmdArg) *Command {
 	// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
-	cargs := make([]string, len(globalCommandArgs))
-	copy(cargs, globalCommandArgs)
+	cargs := make([]string, 0, len(globalCommandArgs)+len(args))
+	for _, arg := range globalCommandArgs {
+		cargs = append(cargs, string(arg))
+	}
+	for _, arg := range args {
+		cargs = append(cargs, string(arg))
+	}
 	return &Command{
 		name:             GitExecutable,
-		args:             append(cargs, args...),
+		args:             cargs,
 		parentContext:    ctx,
 		globalArgsLength: len(globalCommandArgs),
 	}
 }
 
 // NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
-func NewCommandNoGlobals(args ...string) *Command {
+// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
+func NewCommandNoGlobals(args ...CmdArg) *Command {
 	return NewCommandContextNoGlobals(DefaultContext, args...)
 }
 
 // NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
-func NewCommandContextNoGlobals(ctx context.Context, args ...string) *Command {
+// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
+func NewCommandContextNoGlobals(ctx context.Context, args ...CmdArg) *Command {
+	cargs := make([]string, 0, len(args))
+	for _, arg := range args {
+		cargs = append(cargs, string(arg))
+	}
 	return &Command{
 		name:          GitExecutable,
-		args:          args,
+		args:          cargs,
 		parentContext: ctx,
 	}
 }
@@ -89,48 +104,109 @@ func (c *Command) SetDescription(desc string) *Command {
 	return c
 }
 
-// AddArguments adds new argument(s) to the command.
-func (c *Command) AddArguments(args ...string) *Command {
+// AddArguments adds new git argument(s) to the command. Each argument must be safe to be trusted.
+// User-provided arguments should be passed to AddDynamicArguments instead.
+func (c *Command) AddArguments(args ...CmdArg) *Command {
+	for _, arg := range args {
+		c.args = append(c.args, string(arg))
+	}
+	return c
+}
+
+// AddDynamicArguments adds new dynamic argument(s) to the command.
+// The arguments may come from user input and can not be trusted, so no leading '-' is allowed to avoid passing options
+func (c *Command) AddDynamicArguments(args ...string) *Command {
+	for _, arg := range args {
+		if arg != "" && arg[0] == '-' {
+			c.brokenArgs = append(c.brokenArgs, arg)
+		}
+	}
+	if len(c.brokenArgs) != 0 {
+		return c
+	}
 	c.args = append(c.args, args...)
 	return c
 }
 
-// RunOpts represents parameters to run the command
+// AddDashesAndList adds the "--" and then add the list as arguments, it's usually for adding file list
+// At the moment, this function can be only called once, maybe in future it can be refactored to support multiple calls (if necessary)
+func (c *Command) AddDashesAndList(list ...string) *Command {
+	c.args = append(c.args, "--")
+	// Some old code also checks `arg != ""`, IMO it's not necessary.
+	// If the check is needed, the list should be prepared before the call to this function
+	c.args = append(c.args, list...)
+	return c
+}
+
+// CmdArgCheck checks whether the string is safe to be used as a dynamic argument.
+// It panics if the check fails. Usually it should not be used, it's just for refactoring purpose
+// deprecated
+func CmdArgCheck(s string) CmdArg {
+	if s != "" && s[0] == '-' {
+		panic("invalid git cmd argument: " + s)
+	}
+	return CmdArg(s)
+}
+
+// RunOpts represents parameters to run the command. If UseContextTimeout is specified, then Timeout is ignored.
 type RunOpts struct {
-	Env            []string
-	Timeout        time.Duration
-	Dir            string
-	Stdout, Stderr io.Writer
-	Stdin          io.Reader
-	PipelineFunc   func(context.Context, context.CancelFunc) error
+	Env               []string
+	Timeout           time.Duration
+	UseContextTimeout bool
+	Dir               string
+	Stdout, Stderr    io.Writer
+	Stdin             io.Reader
+	PipelineFunc      func(context.Context, context.CancelFunc) error
 }
 
-// CommonGitCmdEnvs returns the common environment variables for a "git" command.
-func CommonGitCmdEnvs() []string {
+func commonBaseEnvs() []string {
 	// at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
-	return []string{
-		fmt.Sprintf("LC_ALL=%s", DefaultLocale),
-		"GIT_TERMINAL_PROMPT=0",    // avoid prompting for credentials interactively, supported since git v2.3
-		"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
+	envs := []string{
 		"HOME=" + HomeDir(),        // make Gitea use internal git config only, to prevent conflicts with user's git config
+		"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
+	}
+
+	// some environment variables should be passed to git command
+	passThroughEnvKeys := []string{
+		"GNUPGHOME", // git may call gnupg to do commit signing
 	}
+	for _, key := range passThroughEnvKeys {
+		if val, ok := os.LookupEnv(key); ok {
+			envs = append(envs, key+"="+val)
+		}
+	}
+	return envs
 }
 
-// CommonCmdServEnvs is like CommonGitCmdEnvs but it only returns minimal required environment variables for the "gitea serv" command
+// CommonGitCmdEnvs returns the common environment variables for a "git" command.
+func CommonGitCmdEnvs() []string {
+	return append(commonBaseEnvs(), []string{
+		"LC_ALL=" + DefaultLocale,
+		"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
+	}...)
+}
+
+// CommonCmdServEnvs is like CommonGitCmdEnvs, but it only returns minimal required environment variables for the "gitea serv" command
 func CommonCmdServEnvs() []string {
-	return []string{
-		"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
-		"HOME=" + HomeDir(),        // make Gitea use internal git config only, to prevent conflicts with user's git config
-	}
+	return commonBaseEnvs()
 }
 
+var ErrBrokenCommand = errors.New("git command is broken")
+
 // Run runs the command with the RunOpts
 func (c *Command) Run(opts *RunOpts) error {
+	if len(c.brokenArgs) != 0 {
+		log.Error("git command is broken: %s, broken args: %s", c.String(), strings.Join(c.brokenArgs, " "))
+		return ErrBrokenCommand
+	}
 	if opts == nil {
 		opts = &RunOpts{}
 	}
-	if opts.Timeout <= 0 {
-		opts.Timeout = defaultCommandExecutionTimeout
+
+	// We must not change the provided options
+	timeout := opts.Timeout
+	if timeout <= 0 {
+		timeout = defaultCommandExecutionTimeout
 	}
 
 	if len(opts.Dir) == 0 {
@@ -158,7 +234,15 @@ func (c *Command) Run(opts *RunOpts) error {
 		desc = fmt.Sprintf("%s %s [repo_path: %s]", c.name, strings.Join(args, " "), opts.Dir)
 	}
 
-	ctx, cancel, finished := process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
+	var ctx context.Context
+	var cancel context.CancelFunc
+	var finished context.CancelFunc
+
+	if opts.UseContextTimeout {
+		ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
+	} else {
+		ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
+	}
 	defer finished()
 
 	cmd := exec.CommandContext(ctx, c.name, c.args...)
@@ -258,9 +342,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
 	}
 	stdoutBuf := &bytes.Buffer{}
 	stderrBuf := &bytes.Buffer{}
-	opts.Stdout = stdoutBuf
-	opts.Stderr = stderrBuf
-	err := c.Run(opts)
+
+	// We must not change the provided options as it could break future calls - therefore make a copy.
+	newOpts := &RunOpts{
+		Env:               opts.Env,
+		Timeout:           opts.Timeout,
+		UseContextTimeout: opts.UseContextTimeout,
+		Dir:               opts.Dir,
+		Stdout:            stdoutBuf,
+		Stderr:            stderrBuf,
+		Stdin:             opts.Stdin,
+		PipelineFunc:      opts.PipelineFunc,
+	}
+
+	err := c.Run(newOpts)
 	stderr = stderrBuf.Bytes()
 	if err != nil {
 		return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
@@ -270,12 +365,12 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
 }
 
 // AllowLFSFiltersArgs return globalCommandArgs with lfs filter, it should only be used for tests
-func AllowLFSFiltersArgs() []string {
+func AllowLFSFiltersArgs() []CmdArg {
 	// Now here we should explicitly allow lfs filters to run
-	filteredLFSGlobalArgs := make([]string, len(globalCommandArgs))
+	filteredLFSGlobalArgs := make([]CmdArg, len(globalCommandArgs))
 	j := 0
 	for _, arg := range globalCommandArgs {
-		if strings.Contains(arg, "lfs") {
+		if strings.Contains(string(arg), "lfs") {
 			j--
 		} else {
 			filteredLFSGlobalArgs[j] = arg
diff --git a/modules/git/command_test.go b/modules/git/command_test.go
index 67d4ca388e2b8..52d25c9c74820 100644
--- a/modules/git/command_test.go
+++ b/modules/git/command_test.go
@@ -26,4 +26,19 @@ func TestRunWithContextStd(t *testing.T) {
 		assert.Contains(t, err.Error(), "exit status 129 - unknown option:")
 		assert.Empty(t, stdout)
 	}
+
+	cmd = NewCommand(context.Background())
+	cmd.AddDynamicArguments("-test")
+	assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
+
+	cmd = NewCommand(context.Background())
+	cmd.AddDynamicArguments("--test")
+	assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
+
+	subCmd := "version"
+	cmd = NewCommand(context.Background()).AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production
+	stdout, stderr, err = cmd.RunStdString(&RunOpts{})
+	assert.NoError(t, err)
+	assert.Empty(t, stderr)
+	assert.Contains(t, stdout, "git version")
 }
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 82b5e0b25d082..061adc1082355 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -80,6 +80,9 @@ func (c *Commit) ParentCount() int {
 
 // GetCommitByPath return the commit of relative path object.
 func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
+	if c.repo.LastCommitCache != nil {
+		return c.repo.LastCommitCache.GetCommitByPath(c.ID.String(), relpath)
+	}
 	return c.repo.getCommitByPathWithID(c.ID, relpath)
 }
 
@@ -89,13 +92,13 @@ func AddChanges(repoPath string, all bool, files ...string) error {
 }
 
 // AddChangesWithArgs marks local changes to be ready for commit.
-func AddChangesWithArgs(repoPath string, globalArgs []string, all bool, files ...string) error {
+func AddChangesWithArgs(repoPath string, globalArgs []CmdArg, all bool, files ...string) error {
 	cmd := NewCommandNoGlobals(append(globalArgs, "add")...)
 	if all {
 		cmd.AddArguments("--all")
 	}
-	cmd.AddArguments("--")
-	_, _, err := cmd.AddArguments(files...).RunStdString(&RunOpts{Dir: repoPath})
+	cmd.AddDashesAndList(files...)
+	_, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
 	return err
 }
 
@@ -109,17 +112,17 @@ type CommitChangesOptions struct {
 // CommitChanges commits local changes with given committer, author and message.
 // If author is nil, it will be the same as committer.
 func CommitChanges(repoPath string, opts CommitChangesOptions) error {
-	cargs := make([]string, len(globalCommandArgs))
+	cargs := make([]CmdArg, len(globalCommandArgs))
 	copy(cargs, globalCommandArgs)
 	return CommitChangesWithArgs(repoPath, cargs, opts)
 }
 
 // CommitChangesWithArgs commits local changes with given committer, author and message.
 // If author is nil, it will be the same as committer.
-func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOptions) error {
+func CommitChangesWithArgs(repoPath string, args []CmdArg, opts CommitChangesOptions) error {
 	cmd := NewCommandNoGlobals(args...)
 	if opts.Committer != nil {
-		cmd.AddArguments("-c", "user.name="+opts.Committer.Name, "-c", "user.email="+opts.Committer.Email)
+		cmd.AddArguments("-c", CmdArg("user.name="+opts.Committer.Name), "-c", CmdArg("user.email="+opts.Committer.Email))
 	}
 	cmd.AddArguments("commit")
 
@@ -127,9 +130,9 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt
 		opts.Author = opts.Committer
 	}
 	if opts.Author != nil {
-		cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email))
+		cmd.AddArguments(CmdArg(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email)))
 	}
-	cmd.AddArguments("-m", opts.Message)
+	cmd.AddArguments("-m").AddDynamicArguments(opts.Message)
 
 	_, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
 	// No stderr but exit status 1 means nothing to commit.
@@ -141,15 +144,13 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt
 
 // AllCommitsCount returns count of all commits in repository
 func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
-	args := []string{"--all", "--count"}
+	cmd := NewCommand(ctx, "rev-list")
 	if hidePRRefs {
-		args = append([]string{"--exclude=" + PullPrefix + "*"}, args...)
+		cmd.AddArguments("--exclude=" + PullPrefix + "*")
 	}
-	cmd := NewCommand(ctx, "rev-list")
-	cmd.AddArguments(args...)
+	cmd.AddArguments("--all", "--count")
 	if len(files) > 0 {
-		cmd.AddArguments("--")
-		cmd.AddArguments(files...)
+		cmd.AddDashesAndList(files...)
 	}
 
 	stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
@@ -163,10 +164,9 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
 // CommitsCountFiles returns number of total commits of until given revision.
 func CommitsCountFiles(ctx context.Context, repoPath string, revision, relpath []string) (int64, error) {
 	cmd := NewCommand(ctx, "rev-list", "--count")
-	cmd.AddArguments(revision...)
+	cmd.AddDynamicArguments(revision...)
 	if len(relpath) > 0 {
-		cmd.AddArguments("--")
-		cmd.AddArguments(relpath...)
+		cmd.AddDashesAndList(relpath...)
 	}
 
 	stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
@@ -206,7 +206,7 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
 		return false, nil
 	}
 
-	_, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
+	_, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
 	if err == nil {
 		return true, nil
 	}
@@ -389,15 +389,12 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
 
 // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
 func (c *Commit) GetBranchName() (string, error) {
-	args := []string{
-		"name-rev",
-	}
+	cmd := NewCommand(c.repo.Ctx, "name-rev")
 	if CheckGitVersionAtLeast("2.13.0") == nil {
-		args = append(args, "--exclude", "refs/tags/*")
+		cmd.AddArguments("--exclude", "refs/tags/*")
 	}
-	args = append(args, "--name-only", "--no-undefined", c.ID.String())
-
-	data, _, err := NewCommand(c.repo.Ctx, args...).RunStdString(&RunOpts{Dir: c.repo.Path})
+	cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
+	data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path})
 	if err != nil {
 		// handle special case where git can not describe commit
 		if strings.Contains(err.Error(), "cannot describe") {
@@ -418,12 +415,12 @@ func (c *Commit) LoadBranchName() (err error) {
 	}
 
 	c.Branch, err = c.GetBranchName()
-	return
+	return err
 }
 
 // GetTagName gets the current tag name for given commit
 func (c *Commit) GetTagName() (string, error) {
-	data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always", c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path})
+	data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always").AddDynamicArguments(c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path})
 	if err != nil {
 		// handle special case where there is no tag for this commit
 		if strings.Contains(err.Error(), "no tag exactly matches") {
@@ -500,9 +497,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
 	}()
 
 	stderr := new(bytes.Buffer)
-	args := []string{"log", "--name-status", "-c", "--pretty=format:", "--parents", "--no-renames", "-z", "-1", commitID}
-
-	err := NewCommand(ctx, args...).Run(&RunOpts{
+	err := NewCommand(ctx, "log", "--name-status", "-c", "--pretty=format:", "--parents", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(&RunOpts{
 		Dir:    repoPath,
 		Stdout: w,
 		Stderr: stderr,
@@ -518,7 +513,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
 
 // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
 func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
-	commitID, _, err := NewCommand(ctx, "rev-parse", shortID).RunStdString(&RunOpts{Dir: repoPath})
+	commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
 	if err != nil {
 		if strings.Contains(err.Error(), "exit status 128") {
 			return "", ErrNotExist{shortID, ""}
diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go
index 91a1804db5cff..341698ab34fe0 100644
--- a/modules/git/commit_info_gogit.go
+++ b/modules/git/commit_info_gogit.go
@@ -17,7 +17,7 @@ import (
 )
 
 // GetCommitsInfo gets information of all commits that are corresponding to these entries
-func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *Commit, error) {
+func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
 	entryPaths := make([]string, len(tes)+1)
 	// Get the commit for the treePath itself
 	entryPaths[0] = ""
@@ -35,15 +35,15 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 		return nil, nil, err
 	}
 
-	var revs map[string]*object.Commit
-	if cache != nil {
+	var revs map[string]*Commit
+	if commit.repo.LastCommitCache != nil {
 		var unHitPaths []string
-		revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, cache)
+		revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
 		if err != nil {
 			return nil, nil, err
 		}
 		if len(unHitPaths) > 0 {
-			revs2, err := GetLastCommitForPaths(ctx, cache, c, treePath, unHitPaths)
+			revs2, err := GetLastCommitForPaths(ctx, commit.repo.LastCommitCache, c, treePath, unHitPaths)
 			if err != nil {
 				return nil, nil, err
 			}
@@ -68,8 +68,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 		}
 
 		// Check if we have found a commit for this entry in time
-		if rev, ok := revs[entry.Name()]; ok {
-			entryCommit := convertCommit(rev)
+		if entryCommit, ok := revs[entry.Name()]; ok {
 			commitsInfo[i].Commit = entryCommit
 		}
 
@@ -96,10 +95,10 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 	// get it for free during the tree traversal and it's used for listing
 	// pages to display information about newest commit for a given path.
 	var treeCommit *Commit
+	var ok bool
 	if treePath == "" {
 		treeCommit = commit
-	} else if rev, ok := revs[""]; ok {
-		treeCommit = convertCommit(rev)
+	} else if treeCommit, ok = revs[""]; ok {
 		treeCommit.repo = commit.repo
 	}
 	return commitsInfo, treeCommit, nil
@@ -155,16 +154,16 @@ func getFileHashes(c cgobject.CommitNode, treePath string, paths []string) (map[
 	return hashes, nil
 }
 
-func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*object.Commit, []string, error) {
+func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
 	var unHitEntryPaths []string
-	results := make(map[string]*object.Commit)
+	results := make(map[string]*Commit)
 	for _, p := range paths {
 		lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 		if err != nil {
 			return nil, nil, err
 		}
 		if lastCommit != nil {
-			results[p] = lastCommit.(*object.Commit)
+			results[p] = lastCommit
 			continue
 		}
 
@@ -175,7 +174,7 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac
 }
 
 // GetLastCommitForPaths returns last commit information
-func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
+func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*Commit, error) {
 	refSha := c.ID().String()
 
 	// We do a tree traversal with nodes sorted by commit time
@@ -293,13 +292,13 @@ heaploop:
 	}
 
 	// Post-processing
-	result := make(map[string]*object.Commit)
+	result := make(map[string]*Commit)
 	for path, commitNode := range resultNodes {
-		var err error
-		result[path], err = commitNode.Commit()
+		commit, err := commitNode.Commit()
 		if err != nil {
 			return nil, err
 		}
+		result[path] = convertCommit(commit)
 	}
 
 	return result, nil
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index f430c672f8845..d7bca3b9486a6 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -17,7 +17,7 @@ import (
 )
 
 // GetCommitsInfo gets information of all commits that are corresponding to these entries
-func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string, cache *LastCommitCache) ([]CommitInfo, *Commit, error) {
+func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath string) ([]CommitInfo, *Commit, error) {
 	entryPaths := make([]string, len(tes)+1)
 	// Get the commit for the treePath itself
 	entryPaths[0] = ""
@@ -28,15 +28,15 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 	var err error
 
 	var revs map[string]*Commit
-	if cache != nil {
+	if commit.repo.LastCommitCache != nil {
 		var unHitPaths []string
-		revs, unHitPaths, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryPaths, cache)
+		revs, unHitPaths, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
 		if err != nil {
 			return nil, nil, err
 		}
 		if len(unHitPaths) > 0 {
 			sort.Strings(unHitPaths)
-			commits, err := GetLastCommitForPaths(ctx, cache, commit, treePath, unHitPaths)
+			commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths)
 			if err != nil {
 				return nil, nil, err
 			}
@@ -47,7 +47,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 		}
 	} else {
 		sort.Strings(entryPaths)
-		revs, err = GetLastCommitForPaths(ctx, nil, commit, treePath, entryPaths)
+		revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
 	}
 	if err != nil {
 		return nil, nil, err
@@ -99,18 +99,15 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 }
 
 func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
-	wr, rd, cancel := cache.repo.CatFileBatch(ctx)
-	defer cancel()
-
 	var unHitEntryPaths []string
 	results := make(map[string]*Commit)
 	for _, p := range paths {
-		lastCommit, err := cache.Get(commitID, path.Join(treePath, p), wr, rd)
+		lastCommit, err := cache.Get(commitID, path.Join(treePath, p))
 		if err != nil {
 			return nil, nil, err
 		}
 		if lastCommit != nil {
-			results[p] = lastCommit.(*Commit)
+			results[p] = lastCommit
 			continue
 		}
 
@@ -121,9 +118,9 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string
 }
 
 // GetLastCommitForPaths returns last commit information
-func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
+func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
 	// We read backwards from the commit to obtain all of the commits
-	revs, err := WalkGitLog(ctx, cache, commit.repo, commit, treePath, paths...)
+	revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...)
 	if err != nil {
 		return nil, err
 	}
@@ -157,7 +154,7 @@ func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *
 		if typ != "commit" {
 			return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
 		}
-		c, err = CommitFromReader(commit.repo, MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size)))
+		c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
 		if err != nil {
 			return nil, err
 		}
diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go
index 49845522a9d53..4bc359689670f 100644
--- a/modules/git/commit_info_test.go
+++ b/modules/git/commit_info_test.go
@@ -6,13 +6,10 @@ package git
 
 import (
 	"context"
-	"os"
 	"path/filepath"
 	"testing"
 	"time"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
 )
 
@@ -20,18 +17,14 @@ const (
 	testReposDir = "tests/repos/"
 )
 
-func cloneRepo(url, name string) (string, error) {
-	repoDir, err := os.MkdirTemp("", name)
-	if err != nil {
-		return "", err
-	}
+func cloneRepo(tb testing.TB, url string) (string, error) {
+	repoDir := tb.TempDir()
 	if err := Clone(DefaultContext, url, repoDir, CloneRepoOptions{
 		Mirror:  false,
 		Bare:    false,
 		Quiet:   true,
 		Timeout: 5 * time.Minute,
 	}); err != nil {
-		_ = util.RemoveAll(repoDir)
 		return "", err
 	}
 	return repoDir, nil
@@ -91,7 +84,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
 		}
 
 		// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
-		commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.TODO(), commit, testCase.Path, nil)
+		commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.TODO(), commit, testCase.Path)
 		assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
 		if err != nil {
 			t.FailNow()
@@ -118,11 +111,10 @@ func TestEntries_GetCommitsInfo(t *testing.T) {
 
 	testGetCommitsInfo(t, bareRepo1)
 
-	clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestEntries_GetCommitsInfo")
+	clonedPath, err := cloneRepo(t, bareRepo1Path)
 	if err != nil {
 		assert.NoError(t, err)
 	}
-	defer util.RemoveAll(clonedPath)
 	clonedRepo1, err := openRepositoryWithDefaultContext(clonedPath)
 	if err != nil {
 		assert.NoError(t, err)
@@ -150,11 +142,10 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 		var commit *Commit
 		var entries Entries
 		var repo *Repository
-		repoPath, err := cloneRepo(benchmark.url, benchmark.name)
+		repoPath, err := cloneRepo(b, benchmark.url)
 		if err != nil {
 			b.Fatal(err)
 		}
-		defer util.RemoveAll(repoPath)
 
 		if repo, err = openRepositoryWithDefaultContext(repoPath); err != nil {
 			b.Fatal(err)
@@ -170,7 +161,7 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
 		b.ResetTimer()
 		b.Run(benchmark.name, func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
-				_, _, err := entries.GetCommitsInfo(context.Background(), commit, "", nil)
+				_, _, err := entries.GetCommitsInfo(context.Background(), commit, "")
 				if err != nil {
 					b.Fatal(err)
 				}
diff --git a/modules/git/diff.go b/modules/git/diff.go
index c9d68bb130fe9..1a43d0dd4ab57 100644
--- a/modules/git/diff.go
+++ b/modules/git/diff.go
@@ -35,13 +35,13 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer
 // GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer.
 func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error {
 	stderr := new(bytes.Buffer)
-	cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R", commitID)
+	cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
 	if err := cmd.Run(&RunOpts{
 		Dir:    repoPath,
 		Stdout: writer,
 		Stderr: stderr,
 	}); err != nil {
-		return fmt.Errorf("Run: %v - %s", err, stderr)
+		return fmt.Errorf("Run: %w - %s", err, stderr)
 	}
 	return nil
 }
@@ -52,45 +52,44 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
 	if err != nil {
 		return err
 	}
-	fileArgs := make([]string, 0)
+	var files []string
 	if len(file) > 0 {
-		fileArgs = append(fileArgs, "--", file)
+		files = append(files, file)
 	}
 
-	var args []string
+	cmd := NewCommand(repo.Ctx)
 	switch diffType {
 	case RawDiffNormal:
 		if len(startCommit) != 0 {
-			args = append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)
+			cmd.AddArguments("diff", "-M").AddDynamicArguments(startCommit, endCommit).AddDashesAndList(files...)
 		} else if commit.ParentCount() == 0 {
-			args = append([]string{"show", endCommit}, fileArgs...)
+			cmd.AddArguments("show").AddDynamicArguments(endCommit).AddDashesAndList(files...)
 		} else {
 			c, _ := commit.Parent(0)
-			args = append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)
+			cmd.AddArguments("diff", "-M").AddDynamicArguments(c.ID.String(), endCommit).AddDashesAndList(files...)
 		}
 	case RawDiffPatch:
 		if len(startCommit) != 0 {
 			query := fmt.Sprintf("%s...%s", endCommit, startCommit)
-			args = append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)
+			cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(query).AddDashesAndList(files...)
 		} else if commit.ParentCount() == 0 {
-			args = append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)
+			cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(endCommit).AddDashesAndList(files...)
 		} else {
 			c, _ := commit.Parent(0)
 			query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
-			args = append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)
+			cmd.AddArguments("format-patch", "--no-signature", "--stdout").AddDynamicArguments(query).AddDashesAndList(files...)
 		}
 	default:
 		return fmt.Errorf("invalid diffType: %s", diffType)
 	}
 
 	stderr := new(bytes.Buffer)
-	cmd := NewCommand(repo.Ctx, args...)
 	if err = cmd.Run(&RunOpts{
 		Dir:    repo.Path,
 		Stdout: writer,
 		Stderr: stderr,
 	}); err != nil {
-		return fmt.Errorf("Run: %v - %s", err, stderr)
+		return fmt.Errorf("Run: %w - %s", err, stderr)
 	}
 	return nil
 }
@@ -115,7 +114,7 @@ func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHu
 		rightLine = leftLine
 		righHunk = leftHunk
 	}
-	return
+	return leftLine, leftHunk, rightLine, righHunk
 }
 
 // Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
@@ -287,7 +286,7 @@ func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []s
 	affectedFiles := make([]string, 0, 32)
 
 	// Run `git diff --name-only` to get the names of the changed files
-	err = NewCommand(repo.Ctx, "diff", "--name-only", oldCommitID, newCommitID).
+	err = NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
 		Run(&RunOpts{
 			Env:    env,
 			Dir:    repo.Path,
diff --git a/modules/git/error.go b/modules/git/error.go
index 387dd724e5852..ebfea8e702c2e 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -8,6 +8,8 @@ import (
 	"fmt"
 	"strings"
 	"time"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrExecTimeout error when exec timed out
@@ -41,6 +43,10 @@ func (err ErrNotExist) Error() string {
 	return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath)
 }
 
+func (err ErrNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrBadLink entry.FollowLink error
 type ErrBadLink struct {
 	Name    string
@@ -87,6 +93,10 @@ func (err ErrBranchNotExist) Error() string {
 	return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
 }
 
+func (err ErrBranchNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrPushOutOfDate represents an error if merging fails due to unrelated histories
 type ErrPushOutOfDate struct {
 	StdOut string
@@ -106,7 +116,7 @@ func (err *ErrPushOutOfDate) Error() string {
 
 // Unwrap unwraps the underlying error
 func (err *ErrPushOutOfDate) Unwrap() error {
-	return fmt.Errorf("%v - %s", err.Err, err.StdErr)
+	return fmt.Errorf("%w - %s", err.Err, err.StdErr)
 }
 
 // ErrPushRejected represents an error if merging fails due to rejection from a hook
@@ -129,7 +139,7 @@ func (err *ErrPushRejected) Error() string {
 
 // Unwrap unwraps the underlying error
 func (err *ErrPushRejected) Unwrap() error {
-	return fmt.Errorf("%v - %s", err.Err, err.StdErr)
+	return fmt.Errorf("%w - %s", err.Err, err.StdErr)
 }
 
 // GenerateMessage generates the remote message from the stderr
diff --git a/modules/git/foreachref/parser.go b/modules/git/foreachref/parser.go
index eb8b77d9038bf..bf83a10ed5a21 100644
--- a/modules/git/foreachref/parser.go
+++ b/modules/git/foreachref/parser.go
@@ -68,8 +68,7 @@ func NewParser(r io.Reader, format Format) *Parser {
 //
 // It could, for example return something like:
 //
-//  { "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
-//
+//	{ "objecttype": "tag", "refname:short": "v1.16.4", "object": "f460b7543ed500e49c133c2cd85c8c55ee9dbe27" }
 func (p *Parser) Next() map[string]string {
 	if !p.scanner.Scan() {
 		return nil
@@ -89,8 +88,7 @@ func (p *Parser) Err() error {
 
 // parseRef parses out all key-value pairs from a single reference block, such as
 //
-//   "objecttype tag\0refname:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27"
-//
+//	"objecttype tag\0refname:short v1.16.4\0object f460b7543ed500e49c133c2cd85c8c55ee9dbe27"
 func (p *Parser) parseRef(refBlock string) (map[string]string, error) {
 	if refBlock == "" {
 		// must be at EOF
diff --git a/modules/git/git.go b/modules/git/git.go
index 3a3663995b3f0..18d62838df166 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -11,9 +11,10 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"path/filepath"
+	"regexp"
 	"runtime"
 	"strings"
-	"sync"
 	"time"
 
 	"code.gitea.io/gitea/modules/log"
@@ -22,8 +23,8 @@ import (
 	"github.com/hashicorp/go-version"
 )
 
-// GitVersionRequired is the minimum Git version required
-const GitVersionRequired = "2.0.0"
+// RequiredVersion is the minimum Git version required
+const RequiredVersion = "2.0.0"
 
 var (
 	// GitExecutable is the command name of git
@@ -41,7 +42,7 @@ var (
 
 // loadGitVersion returns current Git version from shell. Internal usage only.
 func loadGitVersion() (*version.Version, error) {
-	// doesn't need RWMutex because its exec by Init()
+	// doesn't need RWMutex because it's executed by Init()
 	if gitVersion != nil {
 		return gitVersion, nil
 	}
@@ -88,7 +89,7 @@ func SetExecutablePath(path string) error {
 		return fmt.Errorf("unable to load git version: %w", err)
 	}
 
-	versionRequired, err := version.NewVersion(GitVersionRequired)
+	versionRequired, err := version.NewVersion(RequiredVersion)
 	if err != nil {
 		return err
 	}
@@ -102,7 +103,7 @@ func SetExecutablePath(path string) error {
 				moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
 			}
 		}
-		return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), GitVersionRequired, moreHint)
+		return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint)
 	}
 
 	return nil
@@ -125,36 +126,36 @@ func VersionInfo() string {
 }
 
 func checkInit() error {
-	if setting.RepoRootPath == "" {
-		return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
+	if setting.Git.HomePath == "" {
+		return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
 	}
 	if DefaultContext != nil {
-		log.Warn("git module has been initialized already, duplicate init should be fixed")
+		log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
 	}
 	return nil
 }
 
 // HomeDir is the home dir for git to store the global config file used by Gitea internally
 func HomeDir() string {
-	if setting.RepoRootPath == "" {
+	if setting.Git.HomePath == "" {
 		// strict check, make sure the git module is initialized correctly.
-		// attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users.
+		// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
 		// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
-		log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
+		log.Fatal("Unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
 		return ""
 	}
-	return setting.RepoRootPath
+	return setting.Git.HomePath
 }
 
 // InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
-// This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race
-// However, in integration test, the sub-command function may be called in the current process, so the InitSimple would be called multiple times, too
+// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
 func InitSimple(ctx context.Context) error {
 	if err := checkInit(); err != nil {
 		return err
 	}
 
 	DefaultContext = ctx
+	globalCommandArgs = nil
 
 	if setting.Git.Timeout.Default > 0 {
 		defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
@@ -163,49 +164,48 @@ func InitSimple(ctx context.Context) error {
 	return SetExecutablePath(setting.Git.Path)
 }
 
-var initOnce sync.Once
-
-// InitOnceWithSync initializes git module with version check and change global variables, sync gitconfig.
-// This method will update the global variables ONLY ONCE (just like git.CheckLFSVersion -- which is not ideal too),
-// otherwise there will be data-race problem at the moment.
-func InitOnceWithSync(ctx context.Context) (err error) {
+// InitFull initializes git module with version check and change global variables, sync gitconfig.
+// It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables.
+func InitFull(ctx context.Context) (err error) {
 	if err = checkInit(); err != nil {
 		return err
 	}
 
-	initOnce.Do(func() {
-		err = InitSimple(ctx)
-		if err != nil {
-			return
-		}
+	if err = InitSimple(ctx); err != nil {
+		return
+	}
 
-		// Since git wire protocol has been released from git v2.18
-		if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
-			globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
-		}
+	// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
+	if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
+		_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
+	}
 
-		// By default partial clones are disabled, enable them from git v2.22
-		if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
-			globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
-		}
+	// Since git wire protocol has been released from git v2.18
+	if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
+		globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
+	}
 
-		// Explicitly disable credential helper, otherwise Git credentials might leak
-		if CheckGitVersionAtLeast("2.9") == nil {
-			globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
-		}
+	// Explicitly disable credential helper, otherwise Git credentials might leak
+	if CheckGitVersionAtLeast("2.9") == nil {
+		globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
+	}
 
-		SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
-	})
-	if err != nil {
-		return err
+	SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
+
+	if setting.LFS.StartServer {
+		if CheckGitVersionAtLeast("2.1.2") != nil {
+			return errors.New("LFS server support requires Git >= 2.1.2")
+		}
+		globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
 	}
+
 	return syncGitConfig()
 }
 
 // syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
 func syncGitConfig() (err error) {
 	if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
-		return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err)
+		return fmt.Errorf("unable to prepare git home directory %s, err: %w", HomeDir(), err)
 	}
 
 	// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
@@ -281,7 +281,20 @@ func syncGitConfig() (err error) {
 		}
 	}
 
-	return nil
+	// By default partial clones are disabled, enable them from git v2.22
+	if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
+		if err = configSet("uploadpack.allowfilter", "true"); err != nil {
+			return err
+		}
+		err = configSet("uploadpack.allowAnySHA1InWant", "true")
+	} else {
+		if err = configUnsetAll("uploadpack.allowfilter", "true"); err != nil {
+			return err
+		}
+		err = configUnsetAll("uploadpack.allowAnySHA1InWant", "true")
+	}
+
+	return err
 }
 
 // CheckGitVersionAtLeast check git version is at least the constraint version
@@ -300,7 +313,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
 }
 
 func configSet(key, value string) error {
-	stdout, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+	stdout, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 	if err != nil && !err.IsExitCode(1) {
 		return fmt.Errorf("failed to get git config %s, err: %w", key, err)
 	}
@@ -310,7 +323,7 @@ func configSet(key, value string) error {
 		return nil
 	}
 
-	_, _, err = NewCommand(DefaultContext, "config", "--global", key, value).RunStdString(nil)
+	_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
 	if err != nil {
 		return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
 	}
@@ -319,14 +332,14 @@ func configSet(key, value string) error {
 }
 
 func configSetNonExist(key, value string) error {
-	_, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 	if err == nil {
 		// already exist
 		return nil
 	}
 	if err.IsExitCode(1) {
 		// not exist, set new config
-		_, _, err = NewCommand(DefaultContext, "config", "--global", key, value).RunStdString(nil)
+		_, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
 		if err != nil {
 			return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
 		}
@@ -337,14 +350,14 @@ func configSetNonExist(key, value string) error {
 }
 
 func configAddNonExist(key, value string) error {
-	_, _, err := NewCommand(DefaultContext, "config", "--fixed-value", "--get", key, value).RunStdString(nil)
+	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
 	if err == nil {
 		// already exist
 		return nil
 	}
 	if err.IsExitCode(1) {
 		// not exist, add new config
-		_, _, err = NewCommand(DefaultContext, "config", "--global", "--add", key, value).RunStdString(nil)
+		_, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
 		if err != nil {
 			return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
 		}
@@ -354,10 +367,10 @@ func configAddNonExist(key, value string) error {
 }
 
 func configUnsetAll(key, value string) error {
-	_, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 	if err == nil {
 		// exist, need to remove
-		_, _, err = NewCommand(DefaultContext, "config", "--global", "--fixed-value", "--unset-all", key, value).RunStdString(nil)
+		_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
 		if err != nil {
 			return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
 		}
@@ -371,6 +384,6 @@ func configUnsetAll(key, value string) error {
 }
 
 // Fsck verifies the connectivity and validity of the objects in the database
-func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...string) error {
+func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args ...CmdArg) error {
 	return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
 }
diff --git a/modules/git/git_test.go b/modules/git/git_test.go
index 061c876cde0a1..091573787871f 100644
--- a/modules/git/git_test.go
+++ b/modules/git/git_test.go
@@ -21,14 +21,14 @@ import (
 func testRun(m *testing.M) error {
 	_ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`)
 
-	repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
+	gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home")
 	if err != nil {
 		return fmt.Errorf("unable to create temp dir: %w", err)
 	}
-	defer util.RemoveAll(repoRootPath)
-	setting.RepoRootPath = repoRootPath
+	defer util.RemoveAll(gitHomePath)
+	setting.Git.HomePath = gitHomePath
 
-	if err = InitOnceWithSync(context.Background()); err != nil {
+	if err = InitFull(context.Background()); err != nil {
 		return fmt.Errorf("failed to call Init: %w", err)
 	}
 
@@ -78,4 +78,10 @@ func TestGitConfig(t *testing.T) {
 
 	assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
 	assert.False(t, gitConfigContains("key-b = val-2b"))
+
+	assert.NoError(t, configSet("test.key-x", "*"))
+	assert.True(t, gitConfigContains("key-x = *"))
+	assert.NoError(t, configSetNonExist("test.key-x", "*"))
+	assert.NoError(t, configUnsetAll("test.key-x", "*"))
+	assert.False(t, gitConfigContains("key-x = *"))
 }
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index d4ec517b51025..2b51d5972086f 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
 )
 
 // Cache represents a caching interface
@@ -19,16 +20,96 @@ type Cache interface {
 	Get(key string) interface{}
 }
 
-func (c *LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
-	hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, ref, entryPath)))
+func getCacheKey(repoPath, commitID, entryPath string) string {
+	hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
 	return fmt.Sprintf("last_commit:%x", hashBytes)
 }
 
+// LastCommitCache represents a cache to store last commit
+type LastCommitCache struct {
+	repoPath    string
+	ttl         func() int64
+	repo        *Repository
+	commitCache map[string]*Commit
+	cache       Cache
+}
+
+// NewLastCommitCache creates a new last commit cache for repo
+func NewLastCommitCache(count int64, repoPath string, gitRepo *Repository, cache Cache) *LastCommitCache {
+	if cache == nil {
+		return nil
+	}
+	if !setting.CacheService.LastCommit.Enabled || count < setting.CacheService.LastCommit.CommitsCount {
+		return nil
+	}
+
+	return &LastCommitCache{
+		repoPath: repoPath,
+		repo:     gitRepo,
+		ttl:      setting.LastCommitCacheTTLSeconds,
+		cache:    cache,
+	}
+}
+
 // Put put the last commit id with commit and entry path
 func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
 	if c == nil || c.cache == nil {
 		return nil
 	}
 	log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
-	return c.cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl())
+	return c.cache.Put(getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl())
+}
+
+// Get gets the last commit information by commit id and entry path
+func (c *LastCommitCache) Get(ref, entryPath string) (*Commit, error) {
+	if c == nil || c.cache == nil {
+		return nil, nil
+	}
+
+	commitID, ok := c.cache.Get(getCacheKey(c.repoPath, ref, entryPath)).(string)
+	if !ok || commitID == "" {
+		return nil, nil
+	}
+
+	log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, commitID)
+	if c.commitCache != nil {
+		if commit, ok := c.commitCache[commitID]; ok {
+			log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, commitID)
+			return commit, nil
+		}
+	}
+
+	commit, err := c.repo.GetCommit(commitID)
+	if err != nil {
+		return nil, err
+	}
+	if c.commitCache == nil {
+		c.commitCache = make(map[string]*Commit)
+	}
+	c.commitCache[commitID] = commit
+	return commit, nil
+}
+
+// GetCommitByPath gets the last commit for the entry in the provided commit
+func (c *LastCommitCache) GetCommitByPath(commitID, entryPath string) (*Commit, error) {
+	sha1, err := NewIDFromString(commitID)
+	if err != nil {
+		return nil, err
+	}
+
+	lastCommit, err := c.Get(sha1.String(), entryPath)
+	if err != nil || lastCommit != nil {
+		return lastCommit, err
+	}
+
+	lastCommit, err = c.repo.getCommitByPathWithID(sha1, entryPath)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := c.Put(commitID, entryPath, lastCommit.ID.String()); err != nil {
+		log.Error("Unable to cache %s as the last commit for %q in %s %s. Error %v", lastCommit.ID.String(), entryPath, commitID, c.repoPath, err)
+	}
+
+	return lastCommit, nil
 }
diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go
index 8897000350db0..82c76bad20a49 100644
--- a/modules/git/last_commit_cache_gogit.go
+++ b/modules/git/last_commit_cache_gogit.go
@@ -9,71 +9,25 @@ package git
 import (
 	"context"
 
-	"code.gitea.io/gitea/modules/log"
-
-	"github.com/go-git/go-git/v5/plumbing/object"
 	cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph"
 )
 
-// LastCommitCache represents a cache to store last commit
-type LastCommitCache struct {
-	repoPath    string
-	ttl         func() int64
-	repo        *Repository
-	commitCache map[string]*object.Commit
-	cache       Cache
-}
-
-// NewLastCommitCache creates a new last commit cache for repo
-func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl func() int64, cache Cache) *LastCommitCache {
-	if cache == nil {
+// CacheCommit will cache the commit from the gitRepository
+func (c *Commit) CacheCommit(ctx context.Context) error {
+	if c.repo.LastCommitCache == nil {
 		return nil
 	}
-	return &LastCommitCache{
-		repoPath:    repoPath,
-		repo:        gitRepo,
-		commitCache: make(map[string]*object.Commit),
-		ttl:         ttl,
-		cache:       cache,
-	}
-}
-
-// Get get the last commit information by commit id and entry path
-func (c *LastCommitCache) Get(ref, entryPath string) (interface{}, error) {
-	v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
-	if vs, ok := v.(string); ok {
-		log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
-		if commit, ok := c.commitCache[vs]; ok {
-			log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
-			return commit, nil
-		}
-		id, err := c.repo.ConvertToSHA1(vs)
-		if err != nil {
-			return nil, err
-		}
-		commit, err := c.repo.GoGitRepo().CommitObject(id)
-		if err != nil {
-			return nil, err
-		}
-		c.commitCache[vs] = commit
-		return commit, nil
-	}
-	return nil, nil
-}
-
-// CacheCommit will cache the commit from the gitRepository
-func (c *LastCommitCache) CacheCommit(ctx context.Context, commit *Commit) error {
-	commitNodeIndex, _ := commit.repo.CommitNodeIndex()
+	commitNodeIndex, _ := c.repo.CommitNodeIndex()
 
-	index, err := commitNodeIndex.Get(commit.ID)
+	index, err := commitNodeIndex.Get(c.ID)
 	if err != nil {
 		return err
 	}
 
-	return c.recursiveCache(ctx, index, &commit.Tree, "", 1)
+	return c.recursiveCache(ctx, index, &c.Tree, "", 1)
 }
 
-func (c *LastCommitCache) recursiveCache(ctx context.Context, index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
+func (c *Commit) recursiveCache(ctx context.Context, index cgobject.CommitNode, tree *Tree, treePath string, level int) error {
 	if level == 0 {
 		return nil
 	}
@@ -90,7 +44,7 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, index cgobject.Com
 		entryMap[entry.Name()] = entry
 	}
 
-	commits, err := GetLastCommitForPaths(ctx, c, index, treePath, entryPaths)
+	commits, err := GetLastCommitForPaths(ctx, c.repo.LastCommitCache, index, treePath, entryPaths)
 	if err != nil {
 		return err
 	}
diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go
index 030d5486b6302..1f4d693a262d9 100644
--- a/modules/git/last_commit_cache_nogogit.go
+++ b/modules/git/last_commit_cache_nogogit.go
@@ -7,67 +7,18 @@
 package git
 
 import (
-	"bufio"
 	"context"
-
-	"code.gitea.io/gitea/modules/log"
 )
 
-// LastCommitCache represents a cache to store last commit
-type LastCommitCache struct {
-	repoPath    string
-	ttl         func() int64
-	repo        *Repository
-	commitCache map[string]*Commit
-	cache       Cache
-}
-
-// NewLastCommitCache creates a new last commit cache for repo
-func NewLastCommitCache(repoPath string, gitRepo *Repository, ttl func() int64, cache Cache) *LastCommitCache {
-	if cache == nil {
+// CacheCommit will cache the commit from the gitRepository
+func (c *Commit) CacheCommit(ctx context.Context) error {
+	if c.repo.LastCommitCache == nil {
 		return nil
 	}
-	return &LastCommitCache{
-		repoPath:    repoPath,
-		repo:        gitRepo,
-		commitCache: make(map[string]*Commit),
-		ttl:         ttl,
-		cache:       cache,
-	}
-}
-
-// Get get the last commit information by commit id and entry path
-func (c *LastCommitCache) Get(ref, entryPath string, wr WriteCloserError, rd *bufio.Reader) (interface{}, error) {
-	v := c.cache.Get(c.getCacheKey(c.repoPath, ref, entryPath))
-	if vs, ok := v.(string); ok {
-		log.Debug("LastCommitCache hit level 1: [%s:%s:%s]", ref, entryPath, vs)
-		if commit, ok := c.commitCache[vs]; ok {
-			log.Debug("LastCommitCache hit level 2: [%s:%s:%s]", ref, entryPath, vs)
-			return commit, nil
-		}
-		id, err := c.repo.ConvertToSHA1(vs)
-		if err != nil {
-			return nil, err
-		}
-		if _, err := wr.Write([]byte(vs + "\n")); err != nil {
-			return nil, err
-		}
-		commit, err := c.repo.getCommitFromBatchReader(rd, id)
-		if err != nil {
-			return nil, err
-		}
-		c.commitCache[vs] = commit
-		return commit, nil
-	}
-	return nil, nil
-}
-
-// CacheCommit will cache the commit from the gitRepository
-func (c *LastCommitCache) CacheCommit(ctx context.Context, commit *Commit) error {
-	return c.recursiveCache(ctx, commit, &commit.Tree, "", 1)
+	return c.recursiveCache(ctx, &c.Tree, "", 1)
 }
 
-func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tree *Tree, treePath string, level int) error {
+func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string, level int) error {
 	if level == 0 {
 		return nil
 	}
@@ -82,7 +33,7 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tr
 		entryPaths[i] = entry.Name()
 	}
 
-	_, err = WalkGitLog(ctx, c, commit.repo, commit, treePath, entryPaths...)
+	_, err = WalkGitLog(ctx, c.repo, c, treePath, entryPaths...)
 	if err != nil {
 		return err
 	}
@@ -94,7 +45,7 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tr
 			if err != nil {
 				return err
 			}
-			if err := c.recursiveCache(ctx, commit, subTree, treeEntry.Name(), level-1); err != nil {
+			if err := c.recursiveCache(ctx, subTree, treeEntry.Name(), level-1); err != nil {
 				return err
 			}
 		}
diff --git a/modules/git/lfs.go b/modules/git/lfs.go
deleted file mode 100644
index c5d8354b6dc8c..0000000000000
--- a/modules/git/lfs.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package git
-
-import (
-	"sync"
-
-	logger "code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/setting"
-)
-
-var once sync.Once
-
-// CheckLFSVersion will check lfs version, if not satisfied, then disable it.
-func CheckLFSVersion() {
-	if setting.LFS.StartServer {
-		// Disable LFS client hooks if installed for the current OS user
-		// Needs at least git v2.1.2
-		if CheckGitVersionAtLeast("2.1.2") != nil {
-			setting.LFS.StartServer = false
-			logger.Error("LFS server support needs at least Git v2.1.2")
-		} else {
-			once.Do(func() {
-				globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=",
-					"-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
-			})
-		}
-	}
-}
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index ffd0a0991bf43..dee4fc226ef27 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -8,11 +8,14 @@ import (
 	"bufio"
 	"bytes"
 	"context"
+	"errors"
 	"io"
 	"path"
 	"sort"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
+
 	"github.com/djherbis/buffer"
 	"github.com/djherbis/nio/v3"
 )
@@ -32,39 +35,43 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
 		_ = stdoutWriter.Close()
 	}
 
-	args := make([]string, 0, 8+len(paths))
-	args = append(args, "log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z", head, "--")
+	cmd := NewCommand(ctx)
+	cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
+
+	var files []string
 	if len(paths) < 70 {
 		if treepath != "" {
-			args = append(args, treepath)
+			files = append(files, treepath)
 			for _, pth := range paths {
 				if pth != "" {
-					args = append(args, path.Join(treepath, pth))
+					files = append(files, path.Join(treepath, pth))
 				}
 			}
 		} else {
 			for _, pth := range paths {
 				if pth != "" {
-					args = append(args, pth)
+					files = append(files, pth)
 				}
 			}
 		}
 	} else if treepath != "" {
-		args = append(args, treepath)
+		files = append(files, treepath)
 	}
+	cmd.AddDashesAndList(files...)
 
 	go func() {
 		stderr := strings.Builder{}
-		err := NewCommand(ctx, args...).Run(&RunOpts{
+		err := cmd.Run(&RunOpts{
 			Dir:    repository,
 			Stdout: stdoutWriter,
 			Stderr: &stderr,
 		})
 		if err != nil {
 			_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
-		} else {
-			_ = stdoutWriter.Close()
+			return
 		}
+
+		_ = stdoutWriter.Close()
 	}()
 
 	// For simplicities sake we'll us a buffered reader to read from the cat-file --batch
@@ -279,7 +286,7 @@ func (g *LogNameStatusRepoParser) Close() {
 }
 
 // WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files
-func WalkGitLog(ctx context.Context, cache *LastCommitCache, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
+func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
 	headRef := head.ID.String()
 
 	tree, err := head.SubTree(treepath)
@@ -337,7 +344,7 @@ func WalkGitLog(ctx context.Context, cache *LastCommitCache, repo *Repository, h
 	lastEmptyParent := head.ID.String()
 	commitSinceLastEmptyParent := uint64(0)
 	commitSinceNextRestart := uint64(0)
-	parentRemaining := map[string]bool{}
+	parentRemaining := make(container.Set[string])
 
 	changed := make([]bool, len(paths))
 
@@ -354,7 +361,7 @@ heaploop:
 		}
 		current, err := g.Next(treepath, path2idx, changed, maxpathlen)
 		if err != nil {
-			if err == context.DeadlineExceeded {
+			if errors.Is(err, context.DeadlineExceeded) {
 				break heaploop
 			}
 			g.Close()
@@ -363,7 +370,7 @@ heaploop:
 		if current == nil {
 			break heaploop
 		}
-		delete(parentRemaining, current.CommitID)
+		parentRemaining.Remove(current.CommitID)
 		if current.Paths != nil {
 			for i, found := range current.Paths {
 				if !found {
@@ -372,14 +379,14 @@ heaploop:
 				changed[i] = false
 				if results[i] == "" {
 					results[i] = current.CommitID
-					if err := cache.Put(headRef, path.Join(treepath, paths[i]), current.CommitID); err != nil {
+					if err := repo.LastCommitCache.Put(headRef, path.Join(treepath, paths[i]), current.CommitID); err != nil {
 						return nil, err
 					}
 					delete(path2idx, paths[i])
 					remaining--
 					if results[0] == "" {
 						results[0] = current.CommitID
-						if err := cache.Put(headRef, treepath, current.CommitID); err != nil {
+						if err := repo.LastCommitCache.Put(headRef, treepath, current.CommitID); err != nil {
 							return nil, err
 						}
 						delete(path2idx, "")
@@ -408,14 +415,12 @@ heaploop:
 					}
 				}
 				g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...)
-				parentRemaining = map[string]bool{}
+				parentRemaining = make(container.Set[string])
 				nextRestart = (remaining * 3) / 4
 				continue heaploop
 			}
 		}
-		for _, parent := range current.ParentIDs {
-			parentRemaining[parent] = true
-		}
+		parentRemaining.AddMultiple(current.ParentIDs...)
 	}
 	g.Close()
 
diff --git a/modules/git/notes_gogit.go b/modules/git/notes_gogit.go
index 76bc828957b3d..fe6d1f1e580c8 100644
--- a/modules/git/notes_gogit.go
+++ b/modules/git/notes_gogit.go
@@ -83,7 +83,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
 		log.Error("Unable to get the commit for the path %q. Error: %v", path, err)
 		return err
 	}
-	note.Commit = convertCommit(lastCommits[path])
+	note.Commit = lastCommits[path]
 
 	return nil
 }
diff --git a/modules/git/notes_nogogit.go b/modules/git/notes_nogogit.go
index 1476805dcdc03..ba216ce3e4a41 100644
--- a/modules/git/notes_nogogit.go
+++ b/modules/git/notes_nogogit.go
@@ -81,7 +81,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
 		path = path[idx+1:]
 	}
 
-	lastCommits, err := GetLastCommitForPaths(ctx, nil, notes, treePath, []string{path})
+	lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path})
 	if err != nil {
 		log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
 		return err
diff --git a/modules/git/parse_gogit.go b/modules/git/parse_gogit.go
index 409432c5d6a82..4a8dcfdf3537c 100644
--- a/modules/git/parse_gogit.go
+++ b/modules/git/parse_gogit.go
@@ -56,7 +56,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 		}
 		id, err := NewIDFromString(string(data[pos : pos+40]))
 		if err != nil {
-			return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
+			return nil, fmt.Errorf("Invalid ls-tree output: %w", err)
 		}
 		entry.ID = id
 		entry.gogitTreeEntry.Hash = id
@@ -80,7 +80,7 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 		if data[pos] == '"' {
 			entry.gogitTreeEntry.Name, err = strconv.Unquote(string(data[pos:end]))
 			if err != nil {
-				return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
+				return nil, fmt.Errorf("Invalid ls-tree output: %w", err)
 			}
 		} else {
 			entry.gogitTreeEntry.Name = string(data[pos:end])
diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go
index 6dc4900992867..fb5b63def949c 100644
--- a/modules/git/parse_nogogit.go
+++ b/modules/git/parse_nogogit.go
@@ -22,70 +22,72 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 	return parseTreeEntries(data, nil)
 }
 
+var sepSpace = []byte{' '}
+
 func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
-	entries := make([]*TreeEntry, 0, 10)
+	var err error
+	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 	for pos := 0; pos < len(data); {
-		// expect line to be of the form "   \t"
+		// expect line to be of the form:
+		//    \t
+		//   \t
+		posEnd := bytes.IndexByte(data[pos:], '\n')
+		if posEnd == -1 {
+			posEnd = len(data)
+		} else {
+			posEnd += pos
+		}
+		line := data[pos:posEnd]
+		posTab := bytes.IndexByte(line, '\t')
+		if posTab == -1 {
+			return nil, fmt.Errorf("invalid ls-tree output (no tab): %q", line)
+		}
+
 		entry := new(TreeEntry)
 		entry.ptree = ptree
-		if pos+6 > len(data) {
-			return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
+
+		entryAttrs := line[:posTab]
+		entryName := line[posTab+1:]
+
+		entryMode, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
+		_ /* entryType */, entryAttrs, _ = bytes.Cut(entryAttrs, sepSpace) // the type is not used, the mode is enough to determine the type
+		entryObjectID, entryAttrs, _ := bytes.Cut(entryAttrs, sepSpace)
+		if len(entryAttrs) > 0 {
+			entrySize := entryAttrs // the last field is the space-padded-size
+			entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(entrySize)), 10, 64)
+			entry.sized = true
 		}
-		switch string(data[pos : pos+6]) {
+
+		switch string(entryMode) {
 		case "100644":
 			entry.entryMode = EntryModeBlob
-			pos += 12 // skip over "100644 blob "
 		case "100755":
 			entry.entryMode = EntryModeExec
-			pos += 12 // skip over "100755 blob "
 		case "120000":
 			entry.entryMode = EntryModeSymlink
-			pos += 12 // skip over "120000 blob "
 		case "160000":
 			entry.entryMode = EntryModeCommit
-			pos += 14 // skip over "160000 object "
-		case "040000":
+		case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
 			entry.entryMode = EntryModeTree
-			pos += 12 // skip over "040000 tree "
 		default:
-			return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
+			return nil, fmt.Errorf("unknown type: %v", string(entryMode))
 		}
 
-		if pos+40 > len(data) {
-			return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
-		}
-		id, err := NewIDFromString(string(data[pos : pos+40]))
+		entry.ID, err = NewIDFromString(string(entryObjectID))
 		if err != nil {
-			return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
-		}
-		entry.ID = id
-		pos += 41 // skip over sha and trailing space
-
-		end := pos + bytes.IndexByte(data[pos:], '\t')
-		if end < pos {
-			return nil, fmt.Errorf("Invalid ls-tree -l output: %s", string(data))
-		}
-		entry.size, _ = strconv.ParseInt(strings.TrimSpace(string(data[pos:end])), 10, 64)
-		entry.sized = true
-
-		pos = end + 1
-
-		end = pos + bytes.IndexByte(data[pos:], '\n')
-		if end < pos {
-			return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
+			return nil, fmt.Errorf("invalid ls-tree output (invalid object id): %q, err: %w", line, err)
 		}
 
-		// In case entry name is surrounded by double quotes(it happens only in git-shell).
-		if data[pos] == '"' {
-			entry.name, err = strconv.Unquote(string(data[pos:end]))
+		if len(entryName) > 0 && entryName[0] == '"' {
+			entry.name, err = strconv.Unquote(string(entryName))
 			if err != nil {
-				return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
+				return nil, fmt.Errorf("invalid ls-tree output (invalid name): %q, err: %w", line, err)
 			}
 		} else {
-			entry.name = string(data[pos:end])
+			entry.name = string(entryName)
 		}
 
-		pos = end + 1
+		pos = posEnd + 1
 		entries = append(entries, entry)
 	}
 	return entries, nil
@@ -119,7 +121,7 @@ loop:
 			entry.entryMode = EntryModeSymlink
 		case "160000":
 			entry.entryMode = EntryModeCommit
-		case "40000":
+		case "40000", "40755": // git uses 40000 for tree object, but some users may get 40755 for unknown reasons
 			entry.entryMode = EntryModeTree
 		default:
 			log.Debug("Unknown mode: %v", string(mode))
diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go
index 483f96e9a7fdf..cecd3960da9d7 100644
--- a/modules/git/parse_nogogit_test.go
+++ b/modules/git/parse_nogogit_test.go
@@ -12,7 +12,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestParseTreeEntries(t *testing.T) {
+func TestParseTreeEntriesLong(t *testing.T) {
 	testCases := []struct {
 		Input    string
 		Expected []*TreeEntry
@@ -59,11 +59,47 @@ func TestParseTreeEntries(t *testing.T) {
 		assert.NoError(t, err)
 		assert.Len(t, entries, len(testCase.Expected))
 		for i, entry := range entries {
-			assert.EqualValues(t, testCase.Expected[i].ID, entry.ID)
-			assert.EqualValues(t, testCase.Expected[i].name, entry.name)
-			assert.EqualValues(t, testCase.Expected[i].entryMode, entry.entryMode)
-			assert.EqualValues(t, testCase.Expected[i].sized, entry.sized)
-			assert.EqualValues(t, testCase.Expected[i].size, entry.size)
+			assert.EqualValues(t, testCase.Expected[i], entry)
 		}
 	}
 }
+
+func TestParseTreeEntriesShort(t *testing.T) {
+	testCases := []struct {
+		Input    string
+		Expected []*TreeEntry
+	}{
+		{
+			Input: `100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af	README.md
+040000 tree 84b90550547016f73c5dd3f50dea662389e67b6d	assets
+`,
+			Expected: []*TreeEntry{
+				{
+					ID:        MustIDFromString("ea0d83c9081af9500ac9f804101b3fd0a5c293af"),
+					name:      "README.md",
+					entryMode: EntryModeBlob,
+				},
+				{
+					ID:        MustIDFromString("84b90550547016f73c5dd3f50dea662389e67b6d"),
+					name:      "assets",
+					entryMode: EntryModeTree,
+				},
+			},
+		},
+	}
+	for _, testCase := range testCases {
+		entries, err := ParseTreeEntries([]byte(testCase.Input))
+		assert.NoError(t, err)
+		assert.Len(t, entries, len(testCase.Expected))
+		for i, entry := range entries {
+			assert.EqualValues(t, testCase.Expected[i], entry)
+		}
+	}
+}
+
+func TestParseTreeEntriesInvalid(t *testing.T) {
+	// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
+	entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
+	assert.Error(t, err)
+	assert.Len(t, entries, 0)
+}
diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go
index 40dd2bca2936d..c1d4bd16655de 100644
--- a/modules/git/pipeline/catfile.go
+++ b/modules/git/pipeline/catfile.go
@@ -33,7 +33,7 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca
 		Stdout: catFileCheckWriter,
 		Stderr: stderr,
 	}); err != nil {
-		_ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
+		_ = catFileCheckWriter.CloseWithError(fmt.Errorf("git cat-file --batch-check [%s]: %w - %s", tmpBasePath, err, errbuf.String()))
 	}
 }
 
@@ -51,7 +51,7 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip
 		Stderr: stderr,
 	}); err != nil {
 		log.Error("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String())
-		err = fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %v - %s", tmpBasePath, err, errbuf.String())
+		err = fmt.Errorf("git cat-file --batch-check --batch-all-object [%s]: %w - %s", tmpBasePath, err, errbuf.String())
 		_ = catFileCheckWriter.CloseWithError(err)
 		errChan <- err
 	}
@@ -71,7 +71,7 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile
 		Stdin:  shasToBatchReader,
 		Stderr: stderr,
 	}); err != nil {
-		_ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
+		_ = shasToBatchReader.CloseWithError(fmt.Errorf("git rev-list [%s]: %w - %s", tmpBasePath, err, errbuf.String()))
 	}
 }
 
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index a2b5dd0c9698d..061da8ca500c9 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -116,7 +116,7 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
 				continue
 			case "commit":
 				// Read in the commit to get its tree and in case this is one of the last used commits
-				curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, int64(size)))
+				curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size))
 				if err != nil {
 					return nil, err
 				}
diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go
index 8356e70234459..85ba7db23ed52 100644
--- a/modules/git/pipeline/namerev.go
+++ b/modules/git/pipeline/namerev.go
@@ -29,6 +29,6 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS
 		Stdin:  shasToNameReader,
 		Stderr: stderr,
 	}); err != nil {
-		_ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %v - %s", tmpBasePath, err, errbuf.String()))
+		_ = shasToNameReader.CloseWithError(fmt.Errorf("git name-rev [%s]: %w - %s", tmpBasePath, err, errbuf.String()))
 	}
 }
diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go
index 02619cb58304f..93142034ec161 100644
--- a/modules/git/pipeline/revlist.go
+++ b/modules/git/pipeline/revlist.go
@@ -31,7 +31,7 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy
 		Stderr: stderr,
 	}); err != nil {
 		log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String())
-		err = fmt.Errorf("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String())
+		err = fmt.Errorf("git rev-list --objects --all [%s]: %w - %s", basePath, err, errbuf.String())
 		_ = revListWriter.CloseWithError(err)
 		errChan <- err
 	}
@@ -43,14 +43,14 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
 	defer revListWriter.Close()
 	stderr := new(bytes.Buffer)
 	var errbuf strings.Builder
-	cmd := git.NewCommand(ctx, "rev-list", "--objects", headSHA, "--not", baseSHA)
+	cmd := git.NewCommand(ctx, "rev-list", "--objects").AddDynamicArguments(headSHA).AddArguments("--not").AddDynamicArguments(baseSHA)
 	if err := cmd.Run(&git.RunOpts{
 		Dir:    tmpBasePath,
 		Stdout: revListWriter,
 		Stderr: stderr,
 	}); err != nil {
 		log.Error("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String())
-		errChan <- fmt.Errorf("git rev-list [%s]: %v - %s", tmpBasePath, err, errbuf.String())
+		errChan <- fmt.Errorf("git rev-list [%s]: %w - %s", tmpBasePath, err, errbuf.String())
 	}
 }
 
diff --git a/modules/git/ref.go b/modules/git/ref.go
index 9fd071ce58dad..2f459148a22d4 100644
--- a/modules/git/ref.go
+++ b/modules/git/ref.go
@@ -4,7 +4,10 @@
 
 package git
 
-import "strings"
+import (
+	"regexp"
+	"strings"
+)
 
 const (
 	// RemotePrefix is the base directory of the remotes information of git.
@@ -15,6 +18,29 @@ const (
 	pullLen = len(PullPrefix)
 )
 
+// refNamePatternInvalid is regular expression with unallowed characters in git reference name
+// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
+// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
+var refNamePatternInvalid = regexp.MustCompile(
+	`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
+		`(?:^[/.])|` + // Not HasPrefix("/") or "."
+		`(?:/\.)|` + // no "/."
+		`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
+		`(?:\.\.)|` + // no ".." anywhere
+		`(?://)|` + // no "//" anywhere
+		`(?:@{)|` + // no "@{"
+		`(?:[/.]$)|` + // no terminal '/' or '.'
+		`(?:^@$)`) // Not "@"
+
+// IsValidRefPattern ensures that the provided string could be a valid reference
+func IsValidRefPattern(name string) bool {
+	return !refNamePatternInvalid.MatchString(name)
+}
+
+func SanitizeRefPattern(name string) string {
+	return refNamePatternInvalid.ReplaceAllString(name, "_")
+}
+
 // Reference represents a Git ref.
 type Reference struct {
 	Name   string
diff --git a/modules/git/remote.go b/modules/git/remote.go
index cbb4ac6126b86..c416eea136851 100644
--- a/modules/git/remote.go
+++ b/modules/git/remote.go
@@ -14,9 +14,9 @@ import (
 func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
 	var cmd *Command
 	if CheckGitVersionAtLeast("2.7") == nil {
-		cmd = NewCommand(ctx, "remote", "get-url", remoteName)
+		cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
 	} else {
-		cmd = NewCommand(ctx, "config", "--get", "remote."+remoteName+".url")
+		cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
 	}
 
 	result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 3176e276959a0..8ba3ae4fdafd5 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -59,7 +59,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
 
 // IsRepoURLAccessible checks if given repository URL is accessible.
 func IsRepoURLAccessible(ctx context.Context, url string) bool {
-	_, _, err := NewCommand(ctx, "ls-remote", "-q", "-h", url, "HEAD").RunStdString(nil)
+	_, _, err := NewCommand(ctx, "ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(nil)
 	return err == nil
 }
 
@@ -90,7 +90,7 @@ func (repo *Repository) IsEmpty() (bool, error) {
 		if err.Error() == "exit status 1" && errbuf.String() == "" {
 			return true, nil
 		}
-		return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
+		return true, fmt.Errorf("check empty: %w - %s", err, errbuf.String())
 	}
 
 	return strings.TrimSpace(output.String()) == "", nil
@@ -112,13 +112,11 @@ type CloneRepoOptions struct {
 
 // Clone clones original repository to target path.
 func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
-	cargs := make([]string, len(globalCommandArgs))
-	copy(cargs, globalCommandArgs)
-	return CloneWithArgs(ctx, from, to, cargs, opts)
+	return CloneWithArgs(ctx, globalCommandArgs, from, to, opts)
 }
 
 // CloneWithArgs original repository to target path.
-func CloneWithArgs(ctx context.Context, from, to string, args []string, opts CloneRepoOptions) (err error) {
+func CloneWithArgs(ctx context.Context, args []CmdArg, from, to string, opts CloneRepoOptions) (err error) {
 	toDir := path.Dir(to)
 	if err = os.MkdirAll(toDir, os.ModePerm); err != nil {
 		return err
@@ -144,15 +142,15 @@ func CloneWithArgs(ctx context.Context, from, to string, args []string, opts Clo
 		cmd.AddArguments("--no-checkout")
 	}
 	if opts.Depth > 0 {
-		cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
+		cmd.AddArguments("--depth").AddDynamicArguments(strconv.Itoa(opts.Depth))
 	}
 	if opts.Filter != "" {
-		cmd.AddArguments("--filter", opts.Filter)
+		cmd.AddArguments("--filter").AddDynamicArguments(opts.Filter)
 	}
 	if len(opts.Branch) > 0 {
-		cmd.AddArguments("-b", opts.Branch)
+		cmd.AddArguments("-b").AddDynamicArguments(opts.Branch)
 	}
-	cmd.AddArguments("--", from, to)
+	cmd.AddDashesAndList(from, to)
 
 	if strings.Contains(from, "://") && strings.Contains(from, "@") {
 		cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, util.SanitizeCredentialURLs(from), to, opts.Shared, opts.Mirror, opts.Depth))
@@ -203,10 +201,12 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
 	if opts.Mirror {
 		cmd.AddArguments("--mirror")
 	}
-	cmd.AddArguments("--", opts.Remote)
+	remoteBranchArgs := []string{opts.Remote}
 	if len(opts.Branch) > 0 {
-		cmd.AddArguments(opts.Branch)
+		remoteBranchArgs = append(remoteBranchArgs, opts.Branch)
 	}
+	cmd.AddDashesAndList(remoteBranchArgs...)
+
 	if strings.Contains(opts.Remote, "://") && strings.Contains(opts.Remote, "@") {
 		cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, util.SanitizeCredentialURLs(opts.Remote), opts.Force, opts.Mirror))
 	} else {
@@ -251,7 +251,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
 	}
 
 	if errbuf.Len() > 0 && err != nil {
-		return fmt.Errorf("%v - %s", err, errbuf.String())
+		return fmt.Errorf("%w - %s", err, errbuf.String())
 	}
 
 	return err
@@ -276,7 +276,7 @@ type DivergeObject struct {
 
 func checkDivergence(ctx context.Context, repoPath, baseBranch, targetBranch string) (int, error) {
 	branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
-	cmd := NewCommand(ctx, "rev-list", "--count", branches)
+	cmd := NewCommand(ctx, "rev-list", "--count").AddDynamicArguments(branches)
 	stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
 	if err != nil {
 		return -1, err
@@ -319,7 +319,7 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.
 		return err
 	}
 
-	_, _, err = NewCommand(ctx, "reset", "--soft", commit).RunStdString(&RunOpts{Dir: tmp, Env: env})
+	_, _, err = NewCommand(ctx, "reset", "--soft").AddDynamicArguments(commit).RunStdString(&RunOpts{Dir: tmp, Env: env})
 	if err != nil {
 		return err
 	}
@@ -330,7 +330,7 @@ func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.
 	}
 
 	tmpFile := filepath.Join(tmp, "bundle")
-	_, _, err = NewCommand(ctx, "bundle", "create", tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env})
+	_, _, err = NewCommand(ctx, "bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env})
 	if err != nil {
 		return err
 	}
diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go
index 4a97989949f3f..a0cbfba5d965d 100644
--- a/modules/git/repo_archive.go
+++ b/modules/git/repo_archive.go
@@ -44,20 +44,15 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
 		return fmt.Errorf("unknown format: %v", format)
 	}
 
-	args := []string{
-		"archive",
-	}
+	cmd := NewCommand(ctx, "archive")
 	if usePrefix {
-		args = append(args, "--prefix="+filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/")
+		cmd.AddArguments(CmdArg("--prefix=" + filepath.Base(strings.TrimSuffix(repo.Path, ".git")) + "/"))
 	}
-
-	args = append(args,
-		"--format="+format.String(),
-		commitID,
-	)
+	cmd.AddArguments(CmdArg("--format=" + format.String()))
+	cmd.AddDynamicArguments(commitID)
 
 	var stderr strings.Builder
-	err := NewCommand(ctx, args...).Run(&RunOpts{
+	err := cmd.Run(&RunOpts{
 		Dir:    repo.Path,
 		Stdout: target,
 		Stderr: &stderr,
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 596a91e80382a..d9c50be6f7a62 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -20,7 +20,7 @@ import (
 type CheckAttributeOpts struct {
 	CachedOnly    bool
 	AllAttributes bool
-	Attributes    []string
+	Attributes    []CmdArg
 	Filenames     []string
 	IndexFile     string
 	WorkTree      string
@@ -44,31 +44,23 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
 	stdOut := new(bytes.Buffer)
 	stdErr := new(bytes.Buffer)
 
-	cmdArgs := []string{"check-attr", "-z"}
+	cmd := NewCommand(repo.Ctx, "check-attr", "-z")
 
 	if opts.AllAttributes {
-		cmdArgs = append(cmdArgs, "-a")
+		cmd.AddArguments("-a")
 	} else {
 		for _, attribute := range opts.Attributes {
 			if attribute != "" {
-				cmdArgs = append(cmdArgs, attribute)
+				cmd.AddArguments(attribute)
 			}
 		}
 	}
 
 	if opts.CachedOnly {
-		cmdArgs = append(cmdArgs, "--cached")
-	}
-
-	cmdArgs = append(cmdArgs, "--")
-
-	for _, arg := range opts.Filenames {
-		if arg != "" {
-			cmdArgs = append(cmdArgs, arg)
-		}
+		cmd.AddArguments("--cached")
 	}
 
-	cmd := NewCommand(repo.Ctx, cmdArgs...)
+	cmd.AddDashesAndList(opts.Filenames...)
 
 	if err := cmd.Run(&RunOpts{
 		Env:    env,
@@ -76,7 +68,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
 		Stdout: stdOut,
 		Stderr: stdErr,
 	}); err != nil {
-		return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String())
+		return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
 	}
 
 	// FIXME: This is incorrect on versions < 1.8.5
@@ -106,7 +98,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
 // CheckAttributeReader provides a reader for check-attribute content that can be long running
 type CheckAttributeReader struct {
 	// params
-	Attributes []string
+	Attributes []CmdArg
 	Repo       *Repository
 	IndexFile  string
 	WorkTree   string
@@ -122,7 +114,7 @@ type CheckAttributeReader struct {
 
 // Init initializes the CheckAttributeReader
 func (c *CheckAttributeReader) Init(ctx context.Context) error {
-	cmdArgs := []string{"check-attr", "--stdin", "-z"}
+	cmdArgs := []CmdArg{"check-attr", "--stdin", "-z"}
 
 	if len(c.IndexFile) > 0 {
 		cmdArgs = append(cmdArgs, "--cached")
@@ -191,8 +183,8 @@ func (c *CheckAttributeReader) Run() error {
 // CheckPath check attr for given path
 func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
 	defer func() {
-		if err != nil {
-			log.Error("CheckPath returns error: %v", err)
+		if err != nil && err != c.ctx.Err() {
+			log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
 		}
 	}()
 
@@ -334,7 +326,7 @@ func (wr *lineSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
 						wr.tmp = []byte(remaining[3:])
 						break
 					}
-					return l, fmt.Errorf("unexpected tail %s", string(remaining))
+					return l, fmt.Errorf("unexpected tail %s", remaining)
 				}
 				_, _ = sb.WriteRune(rn)
 				remaining = tail
@@ -401,7 +393,7 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
 	}
 
 	checker := &CheckAttributeReader{
-		Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
+		Attributes: []CmdArg{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
 		Repo:       repo,
 		IndexFile:  indexFilename,
 		WorkTree:   worktree,
diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go
index cd2ca25dfbadf..8fe9c404c3dbb 100644
--- a/modules/git/repo_base_gogit.go
+++ b/modules/git/repo_base_gogit.go
@@ -31,7 +31,8 @@ type Repository struct {
 	gogitStorage *filesystem.Storage
 	gpgSettings  *GPGSettings
 
-	Ctx context.Context
+	Ctx             context.Context
+	LastCommitCache *LastCommitCache
 }
 
 // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
@@ -79,6 +80,8 @@ func (repo *Repository) Close() (err error) {
 	if err := repo.gogitStorage.Close(); err != nil {
 		gitealog.Error("Error closing storage: %v", err)
 	}
+	repo.LastCommitCache = nil
+	repo.tagCache = nil
 	return
 }
 
diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go
index df24d952a8f7a..56af2c640fd4a 100644
--- a/modules/git/repo_base_nogogit.go
+++ b/modules/git/repo_base_nogogit.go
@@ -32,7 +32,8 @@ type Repository struct {
 	checkReader *bufio.Reader
 	checkWriter WriteCloserError
 
-	Ctx context.Context
+	Ctx             context.Context
+	LastCommitCache *LastCommitCache
 }
 
 // openRepositoryWithDefaultContext opens the repository at the given path with DefaultContext.
@@ -101,5 +102,7 @@ func (repo *Repository) Close() (err error) {
 		repo.checkReader = nil
 		repo.checkWriter = nil
 	}
-	return
+	repo.LastCommitCache = nil
+	repo.tagCache = nil
+	return err
 }
diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go
index 6fe6d235ba925..8a3707aa09183 100644
--- a/modules/git/repo_blame.go
+++ b/modules/git/repo_blame.go
@@ -8,13 +8,16 @@ import "fmt"
 
 // FileBlame return the Blame object of file
 func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "blame", "--root", "--", file).RunStdBytes(&RunOpts{Dir: path})
+	stdout, _, err := NewCommand(repo.Ctx, "blame", "--root").AddDashesAndList(file).RunStdBytes(&RunOpts{Dir: path})
 	return stdout, err
 }
 
 // LineBlame returns the latest commit at the given line
 func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
-	res, _, err := NewCommand(repo.Ctx, "blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunStdString(&RunOpts{Dir: path})
+	res, _, err := NewCommand(repo.Ctx, "blame").
+		AddArguments(CmdArg(fmt.Sprintf("-L %d,%d", line, line))).
+		AddArguments("-p").AddDynamicArguments(revision).
+		AddDashesAndList(file).RunStdString(&RunOpts{Dir: path})
 	if err != nil {
 		return nil, err
 	}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
index 8e455480e7279..a3fc7e0c4216b 100644
--- a/modules/git/repo_branch.go
+++ b/modules/git/repo_branch.go
@@ -7,6 +7,7 @@ package git
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 )
@@ -24,7 +25,7 @@ const PullRequestPrefix = "refs/for/"
 
 // IsReferenceExist returns true if given reference exists in the repository.
 func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
-	_, _, err := NewCommand(ctx, "show-ref", "--verify", "--", name).RunStdString(&RunOpts{Dir: repoPath})
+	_, _, err := NewCommand(ctx, "show-ref", "--verify").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repoPath})
 	return err == nil
 }
 
@@ -65,14 +66,21 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
 
 // SetDefaultBranch sets default branch of repository.
 func (repo *Repository) SetDefaultBranch(name string) error {
-	_, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD", BranchPrefix+name).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").AddDynamicArguments(BranchPrefix + name).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
 // GetDefaultBranch gets default branch of repository.
 func (repo *Repository) GetDefaultBranch() (string, error) {
 	stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path})
-	return stdout, err
+	if err != nil {
+		return "", err
+	}
+	stdout = strings.TrimSpace(stdout)
+	if !strings.HasPrefix(stdout, BranchPrefix) {
+		return "", errors.New("the HEAD is not a branch: " + stdout)
+	}
+	return strings.TrimPrefix(stdout, BranchPrefix), nil
 }
 
 // GetBranch returns a branch by it's name
@@ -133,7 +141,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
 		cmd.AddArguments("-d")
 	}
 
-	cmd.AddArguments("--", name)
+	cmd.AddDashesAndList(name)
 	_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
 
 	return err
@@ -142,7 +150,7 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
 // CreateBranch create a new branch
 func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
 	cmd := NewCommand(repo.Ctx, "branch")
-	cmd.AddArguments("--", branch, oldbranchOrCommit)
+	cmd.AddDashesAndList(branch, oldbranchOrCommit)
 
 	_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
 
@@ -155,7 +163,7 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
 	if fetch {
 		cmd.AddArguments("-f")
 	}
-	cmd.AddArguments(name, url)
+	cmd.AddDynamicArguments(name, url)
 
 	_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
 	return err
@@ -163,7 +171,7 @@ func (repo *Repository) AddRemote(name, url string, fetch bool) error {
 
 // RemoveRemote removes a remote from repository.
 func (repo *Repository) RemoveRemote(name string) error {
-	_, _, err := NewCommand(repo.Ctx, "remote", "rm", name).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "remote", "rm").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
@@ -174,6 +182,6 @@ func (branch *Branch) GetCommit() (*Commit, error) {
 
 // RenameBranch rename a branch
 func (repo *Repository) RenameBranch(from, to string) error {
-	_, _, err := NewCommand(repo.Ctx, "branch", "-m", from, to).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "branch", "-m").AddDynamicArguments(from, to).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go
index bc58991085b78..26e20ba116684 100644
--- a/modules/git/repo_branch_nogogit.go
+++ b/modules/git/repo_branch_nogogit.go
@@ -53,7 +53,7 @@ func (repo *Repository) IsReferenceExist(name string) bool {
 
 // IsBranchExist returns true if given branch exists in current repository.
 func (repo *Repository) IsBranchExist(name string) bool {
-	if name == "" {
+	if repo == nil || name == "" {
 		return false
 	}
 
@@ -63,42 +63,40 @@ func (repo *Repository) IsBranchExist(name string) bool {
 // GetBranchNames returns branches from the repository, skipping skip initial branches and
 // returning at most limit branches, or all branches if limit is 0.
 func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
-	return callShowRef(repo.Ctx, repo.Path, BranchPrefix, "--heads", skip, limit)
+	return callShowRef(repo.Ctx, repo.Path, BranchPrefix, []CmdArg{BranchPrefix, "--sort=-committerdate"}, skip, limit)
 }
 
 // WalkReferences walks all the references from the repository
 func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) {
-	return walkShowRef(ctx, repoPath, "", 0, 0, walkfn)
+	return walkShowRef(ctx, repoPath, nil, 0, 0, walkfn)
 }
 
 // WalkReferences walks all the references from the repository
 // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty.
 func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) {
-	var arg string
+	var args []CmdArg
 	switch refType {
 	case ObjectTag:
-		arg = "--tags"
+		args = []CmdArg{TagPrefix, "--sort=-taggerdate"}
 	case ObjectBranch:
-		arg = "--heads"
-	default:
-		arg = ""
+		args = []CmdArg{BranchPrefix, "--sort=-committerdate"}
 	}
 
-	return walkShowRef(repo.Ctx, repo.Path, arg, skip, limit, walkfn)
+	return walkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn)
 }
 
 // callShowRef return refs, if limit = 0 it will not limit
-func callShowRef(ctx context.Context, repoPath, prefix, arg string, skip, limit int) (branchNames []string, countAll int, err error) {
-	countAll, err = walkShowRef(ctx, repoPath, arg, skip, limit, func(_, branchName string) error {
-		branchName = strings.TrimPrefix(branchName, prefix)
+func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs []CmdArg, skip, limit int) (branchNames []string, countAll int, err error) {
+	countAll, err = walkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error {
+		branchName = strings.TrimPrefix(branchName, trimPrefix)
 		branchNames = append(branchNames, branchName)
 
 		return nil
 	})
-	return
+	return branchNames, countAll, err
 }
 
-func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
+func walkShowRef(ctx context.Context, repoPath string, extraArgs []CmdArg, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) {
 	stdoutReader, stdoutWriter := io.Pipe()
 	defer func() {
 		_ = stdoutReader.Close()
@@ -107,10 +105,8 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
 
 	go func() {
 		stderrBuilder := &strings.Builder{}
-		args := []string{"show-ref"}
-		if arg != "" {
-			args = append(args, arg)
-		}
+		args := []CmdArg{"for-each-ref", "--format=%(objectname) %(refname)"}
+		args = append(args, extraArgs...)
 		err := NewCommand(ctx, args...).Run(&RunOpts{
 			Dir:    repoPath,
 			Stdout: stdoutWriter,
@@ -194,7 +190,7 @@ func walkShowRef(ctx context.Context, repoPath, arg string, skip, limit int, wal
 // GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash
 func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) {
 	var revList []string
-	_, err := walkShowRef(repo.Ctx, repo.Path, "", 0, 0, func(walkSha, refname string) error {
+	_, err := walkShowRef(repo.Ctx, repo.Path, nil, 0, 0, func(walkSha, refname string) error {
 		if walkSha == sha && strings.HasPrefix(refname, prefix) {
 			revList = append(revList, refname)
 		}
diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go
index 56f7387097df7..58a738e28bc63 100644
--- a/modules/git/repo_branch_test.go
+++ b/modules/git/repo_branch_test.go
@@ -22,14 +22,14 @@ func TestRepository_GetBranches(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Len(t, branches, 2)
 	assert.EqualValues(t, 3, countAll)
-	assert.ElementsMatch(t, []string{"branch1", "branch2"}, branches)
+	assert.ElementsMatch(t, []string{"master", "branch2"}, branches)
 
 	branches, countAll, err = bareRepo1.GetBranchNames(0, 0)
 
 	assert.NoError(t, err)
 	assert.Len(t, branches, 3)
 	assert.EqualValues(t, 3, countAll)
-	assert.ElementsMatch(t, []string{"branch1", "branch2", "master"}, branches)
+	assert.ElementsMatch(t, []string{"master", "branch2", "branch1"}, branches)
 
 	branches, countAll, err = bareRepo1.GetBranchNames(5, 1)
 
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index e6fec4d1a32e2..90259fd74664c 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -7,10 +7,13 @@ package git
 
 import (
 	"bytes"
+	"encoding/hex"
+	"fmt"
 	"io"
 	"strconv"
 	"strings"
 
+	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/setting"
 )
 
@@ -58,7 +61,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
 		relpath = `\` + relpath
 	}
 
-	stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, id.String(), "--", relpath).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(&RunOpts{Dir: repo.Path})
 	if runErr != nil {
 		return nil, runErr
 	}
@@ -73,7 +76,7 @@ func (repo *Repository) getCommitByPathWithID(id SHA1, relpath string) (*Commit,
 
 // GetCommitByPath returns the last commit of relative path.
 func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
-	stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat, "--", relpath).RunStdBytes(&RunOpts{Dir: repo.Path})
+	stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(&RunOpts{Dir: repo.Path})
 	if runErr != nil {
 		return nil, runErr
 	}
@@ -86,8 +89,10 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
 }
 
 func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "log", id.String(), "--skip="+strconv.Itoa((page-1)*pageSize),
-		"--max-count="+strconv.Itoa(pageSize), prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "log").
+		AddArguments(CmdArg("--skip="+strconv.Itoa((page-1)*pageSize)), CmdArg("--max-count="+strconv.Itoa(pageSize)), prettyLogFormat).
+		AddDynamicArguments(id.String()).
+		RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
 	}
@@ -96,30 +101,30 @@ func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit,
 
 func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Commit, error) {
 	// create new git log command with limit of 100 commis
-	cmd := NewCommand(repo.Ctx, "log", id.String(), "-100", prettyLogFormat)
+	cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
 	// ignore case
-	args := []string{"-i"}
+	args := []CmdArg{"-i"}
 
 	// add authors if present in search query
 	if len(opts.Authors) > 0 {
 		for _, v := range opts.Authors {
-			args = append(args, "--author="+v)
+			args = append(args, CmdArg("--author="+v))
 		}
 	}
 
 	// add committers if present in search query
 	if len(opts.Committers) > 0 {
 		for _, v := range opts.Committers {
-			args = append(args, "--committer="+v)
+			args = append(args, CmdArg("--committer="+v))
 		}
 	}
 
 	// add time constraints if present in search query
 	if len(opts.After) > 0 {
-		args = append(args, "--after="+opts.After)
+		args = append(args, CmdArg("--after="+opts.After))
 	}
 	if len(opts.Before) > 0 {
-		args = append(args, "--before="+opts.Before)
+		args = append(args, CmdArg("--before="+opts.Before))
 	}
 
 	// pretend that all refs along with HEAD were listed on command line as 
@@ -133,7 +138,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
 	// note this is done only for command created above
 	if len(opts.Keywords) > 0 {
 		for _, v := range opts.Keywords {
-			cmd.AddArguments("--grep=" + v)
+			cmd.AddArguments(CmdArg("--grep=" + v))
 		}
 	}
 
@@ -151,14 +156,14 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
 	// then let's iterate over them
 	if len(opts.Keywords) > 0 {
 		for _, v := range opts.Keywords {
-			// ignore anything below 4 characters as too unspecific
-			if len(v) >= 4 {
+			// ignore anything not matching a valid sha pattern
+			if IsValidSHAPattern(v) {
 				// create new git log command with 1 commit limit
 				hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat)
 				// add previous arguments except for --grep and --all
 				hashCmd.AddArguments(args...)
 				// add keyword as 
-				hashCmd.AddArguments(v)
+				hashCmd.AddDynamicArguments(v)
 
 				// search with given constraints for commit matching sha hash of v
 				hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path})
@@ -175,7 +180,7 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co
 }
 
 func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", id1, id2).RunStdBytes(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(id1, id2).RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
 	}
@@ -185,7 +190,7 @@ func (repo *Repository) getFilesChanged(id1, id2 string) ([]string, error) {
 // FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
 // You must ensure that id1 and id2 are valid commit ids.
 func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z", id1, id2, "--", filename).RunStdBytes(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return false, err
 	}
@@ -208,14 +213,16 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
 	}()
 	go func() {
 		stderr := strings.Builder{}
-		err := NewCommand(repo.Ctx, "log", revision, "--follow",
-			"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize*page),
-			prettyLogFormat, "--", file).
-			Run(&RunOpts{
-				Dir:    repo.Path,
-				Stdout: stdoutWriter,
-				Stderr: &stderr,
-			})
+		gitCmd := NewCommand(repo.Ctx, "rev-list").
+			AddArguments(CmdArg("--max-count=" + strconv.Itoa(setting.Git.CommitsRangeSize*page))).
+			AddArguments(CmdArg("--skip=" + strconv.Itoa(skip)))
+		gitCmd.AddDynamicArguments(revision)
+		gitCmd.AddDashesAndList(file)
+		err := gitCmd.Run(&RunOpts{
+			Dir:    repo.Path,
+			Stdout: stdoutWriter,
+			Stderr: &stderr,
+		})
 		if err != nil {
 			_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
 		} else {
@@ -223,41 +230,39 @@ func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (
 		}
 	}()
 
-	if skip > 0 {
-		_, err := io.CopyN(io.Discard, stdoutReader, int64(skip*41))
-		if err != nil {
+	commits := []*Commit{}
+	shaline := [41]byte{}
+	var sha1 SHA1
+	for {
+		n, err := io.ReadFull(stdoutReader, shaline[:])
+		if err != nil || n < 40 {
 			if err == io.EOF {
-				return []*Commit{}, nil
+				err = nil
 			}
-			_ = stdoutReader.CloseWithError(err)
+			return commits, err
+		}
+		n, err = hex.Decode(sha1[:], shaline[0:40])
+		if n != 20 {
+			err = fmt.Errorf("invalid sha %q", string(shaline[:40]))
+		}
+		if err != nil {
 			return nil, err
 		}
+		commit, err := repo.getCommit(sha1)
+		if err != nil {
+			return nil, err
+		}
+		commits = append(commits, commit)
 	}
-
-	stdout, err := io.ReadAll(stdoutReader)
-	if err != nil {
-		return nil, err
-	}
-	return repo.parsePrettyFormatLogToList(stdout)
-}
-
-// CommitsByFileAndRangeNoFollow return the commits according revision file and the page
-func (repo *Repository) CommitsByFileAndRangeNoFollow(revision, file string, page int) ([]*Commit, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "log", revision, "--skip="+strconv.Itoa((page-1)*50),
-		"--max-count="+strconv.Itoa(setting.Git.CommitsRangeSize), prettyLogFormat, "--", file).RunStdBytes(&RunOpts{Dir: repo.Path})
-	if err != nil {
-		return nil, err
-	}
-	return repo.parsePrettyFormatLogToList(stdout)
 }
 
 // FilesCountBetween return the number of files changed between two commits
 func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", startCommitID+"..."+endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID + "..." + endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil && strings.Contains(err.Error(), "no merge base") {
 		// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
 		// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
-		stdout, _, err = NewCommand(repo.Ctx, "diff", "--name-only", startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
+		stdout, _, err = NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
 	}
 	if err != nil {
 		return 0, err
@@ -271,13 +276,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
 	var stdout []byte
 	var err error
 	if before == nil {
-		stdout, _, err = NewCommand(repo.Ctx, "rev-list", last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+		stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 	} else {
-		stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+		stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 		if err != nil && strings.Contains(err.Error(), "no merge base") {
 			// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
 			// previously it would return the results of git rev-list before last so let's try that...
-			stdout, _, err = NewCommand(repo.Ctx, "rev-list", before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+			stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 		}
 	}
 	if err != nil {
@@ -291,13 +296,22 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
 	var stdout []byte
 	var err error
 	if before == nil {
-		stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+		stdout, _, err = NewCommand(repo.Ctx, "rev-list",
+			"--max-count", CmdArg(strconv.Itoa(limit)),
+			"--skip", CmdArg(strconv.Itoa(skip))).
+			AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 	} else {
-		stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String()+".."+last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+		stdout, _, err = NewCommand(repo.Ctx, "rev-list",
+			"--max-count", CmdArg(strconv.Itoa(limit)),
+			"--skip", CmdArg(strconv.Itoa(skip))).
+			AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 		if err != nil && strings.Contains(err.Error(), "no merge base") {
 			// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
 			// previously it would return the results of git rev-list --max-count n before last so let's try that...
-			stdout, _, err = NewCommand(repo.Ctx, "rev-list", "--max-count", strconv.Itoa(limit), "--skip", strconv.Itoa(skip), before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+			stdout, _, err = NewCommand(repo.Ctx, "rev-list",
+				"--max-count", CmdArg(strconv.Itoa(limit)),
+				"--skip", CmdArg(strconv.Itoa(skip))).
+				AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
 		}
 	}
 	if err != nil {
@@ -338,9 +352,9 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
 func (repo *Repository) commitsBefore(id SHA1, limit int) ([]*Commit, error) {
 	cmd := NewCommand(repo.Ctx, "log")
 	if limit > 0 {
-		cmd.AddArguments("-"+strconv.Itoa(limit), prettyLogFormat, id.String())
+		cmd.AddArguments(CmdArg("-"+strconv.Itoa(limit)), prettyLogFormat).AddDynamicArguments(id.String())
 	} else {
-		cmd.AddArguments(prettyLogFormat, id.String())
+		cmd.AddArguments(prettyLogFormat).AddDynamicArguments(id.String())
 	}
 
 	stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
@@ -380,7 +394,11 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) ([]*Commit, erro
 
 func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
 	if CheckGitVersionAtLeast("2.7.0") == nil {
-		stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunStdString(&RunOpts{Dir: repo.Path})
+		stdout, _, err := NewCommand(repo.Ctx, "for-each-ref",
+			CmdArg("--count="+strconv.Itoa(limit)),
+			"--format=%(refname:strip=2)", "--contains").
+			AddDynamicArguments(commit.ID.String(), BranchPrefix).
+			RunStdString(&RunOpts{Dir: repo.Path})
 		if err != nil {
 			return nil, err
 		}
@@ -389,7 +407,7 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error)
 		return branches, nil
 	}
 
-	stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains").AddDynamicArguments(commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
 	}
@@ -428,9 +446,26 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
 
 // IsCommitInBranch check if the commit is on the branch
 func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
-	stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains", commitID, branch).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return false, err
 	}
 	return len(stdout) > 0, err
 }
+
+func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error {
+	if repo.LastCommitCache == nil {
+		commitsCount, err := cache.GetInt64(cacheKey, func() (int64, error) {
+			commit, err := repo.GetCommit(sha)
+			if err != nil {
+				return 0, err
+			}
+			return commit.CommitsCount()
+		})
+		if err != nil {
+			return err
+		}
+		repo.LastCommitCache = NewLastCommitCache(commitsCount, fullName, repo, cache.GetCache())
+	}
+	return nil
+}
diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go
index 9333b0d7b756b..7a869b38b6f64 100644
--- a/modules/git/repo_commit_gogit.go
+++ b/modules/git/repo_commit_gogit.go
@@ -42,14 +42,14 @@ func (repo *Repository) RemoveReference(name string) error {
 
 // ConvertToSHA1 returns a Hash object from a potential ID string
 func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
-	if len(commitID) == 40 {
+	if len(commitID) == SHAFullLength {
 		sha1, err := NewIDFromString(commitID)
 		if err == nil {
 			return sha1, nil
 		}
 	}
 
-	actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", commitID).RunStdString(&RunOpts{Dir: repo.Path})
+	actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		if strings.Contains(err.Error(), "unknown revision or path") ||
 			strings.Contains(err.Error(), "fatal: Needed a single revision") {
diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go
index e528af0ffb88e..2d91ee0955119 100644
--- a/modules/git/repo_commit_nogogit.go
+++ b/modules/git/repo_commit_nogogit.go
@@ -17,7 +17,7 @@ import (
 
 // ResolveReference resolves a name to a reference
 func (repo *Repository) ResolveReference(name string) (string, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash", name).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		if strings.Contains(err.Error(), "not a valid ref") {
 			return "", ErrNotExist{name, ""}
@@ -50,19 +50,19 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
 
 // SetReference sets the commit ID string of given reference (e.g. branch or tag).
 func (repo *Repository) SetReference(name, commitID string) error {
-	_, _, err := NewCommand(repo.Ctx, "update-ref", name, commitID).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "update-ref").AddDynamicArguments(name, commitID).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
 // RemoveReference removes the given reference (e.g. branch or tag).
 func (repo *Repository) RemoveReference(name string) error {
-	_, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d", name).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
 // IsCommitExist returns true if given commit exists in current repository.
 func (repo *Repository) IsCommitExist(name string) bool {
-	_, _, err := NewCommand(repo.Ctx, "cat-file", "-e", name).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
 	return err == nil
 }
 
@@ -138,7 +138,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
 
 // ConvertToSHA1 returns a Hash object from a potential ID string
 func (repo *Repository) ConvertToSHA1(commitID string) (SHA1, error) {
-	if len(commitID) == 40 && SHAPattern.MatchString(commitID) {
+	if len(commitID) == SHAFullLength && IsValidSHAPattern(commitID) {
 		sha1, err := NewIDFromString(commitID)
 		if err == nil {
 			return sha1, nil
diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go
index 3c7af73000e45..7575b116586be 100644
--- a/modules/git/repo_compare.go
+++ b/modules/git/repo_compare.go
@@ -40,13 +40,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
 	if tmpRemote != "origin" {
 		tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
 		// Fetch commit into a temporary branch in order to be able to handle commits and tags
-		_, _, err := NewCommand(repo.Ctx, "fetch", tmpRemote, base+":"+tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path})
+		_, _, err := NewCommand(repo.Ctx, "fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base + ":" + tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path})
 		if err == nil {
 			base = tmpBaseName
 		}
 	}
 
-	stdout, _, err := NewCommand(repo.Ctx, "merge-base", "--", base, head).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "merge-base").AddDashesAndList(base, head).RunStdString(&RunOpts{Dir: repo.Path})
 	return strings.TrimSpace(stdout), base, err
 }
 
@@ -62,7 +62,7 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
 		// Add a temporary remote
 		tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10)
 		if err = repo.AddRemote(tmpRemote, basePath, false); err != nil {
-			return nil, fmt.Errorf("AddRemote: %v", err)
+			return nil, fmt.Errorf("AddRemote: %w", err)
 		}
 		defer func() {
 			if err := repo.RemoveRemote(tmpRemote); err != nil {
@@ -94,13 +94,13 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
 		// We have a common base - therefore we know that ... should work
 		if !fileOnly {
 			var logs []byte
-			logs, _, err = NewCommand(repo.Ctx, "log", baseCommitID+separator+headBranch, prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path})
+			logs, _, err = NewCommand(repo.Ctx, "log").AddDynamicArguments(baseCommitID + separator + headBranch).AddArguments(prettyLogFormat).RunStdBytes(&RunOpts{Dir: repo.Path})
 			if err != nil {
 				return nil, err
 			}
 			compareInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
 			if err != nil {
-				return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
+				return nil, fmt.Errorf("parsePrettyFormatLogToList: %w", err)
 			}
 		} else {
 			compareInfo.Commits = []*Commit{}
@@ -132,7 +132,7 @@ type lineCountWriter struct {
 func (l *lineCountWriter) Write(p []byte) (n int, err error) {
 	n = len(p)
 	l.numLines += bytes.Count(p, []byte{'\000'})
-	return
+	return n, err
 }
 
 // GetDiffNumChangedFiles counts the number of changed files
@@ -147,7 +147,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
 		separator = ".."
 	}
 
-	if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only", base+separator+head).
+	if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base + separator + head).
 		Run(&RunOpts{
 			Dir:    repo.Path,
 			Stdout: w,
@@ -158,7 +158,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
 			// previously it would return the results of git diff -z --name-only base head so let's try that...
 			w = &lineCountWriter{}
 			stderr.Reset()
-			if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only", base, head).Run(&RunOpts{
+			if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base, head).Run(&RunOpts{
 				Dir:    repo.Path,
 				Stdout: w,
 				Stderr: stderr,
@@ -166,27 +166,27 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
 				return w.numLines, nil
 			}
 		}
-		return 0, fmt.Errorf("%v: Stderr: %s", err, stderr)
+		return 0, fmt.Errorf("%w: Stderr: %s", err, stderr)
 	}
 	return w.numLines, nil
 }
 
 // GetDiffShortStat counts number of changed files, number of additions and deletions
 func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) {
-	numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, base+"..."+head)
+	numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base+"..."+head))
 	if err != nil && strings.Contains(err.Error(), "no merge base") {
-		return GetDiffShortStat(repo.Ctx, repo.Path, base, head)
+		return GetDiffShortStat(repo.Ctx, repo.Path, CmdArgCheck(base), CmdArgCheck(head))
 	}
-	return
+	return numFiles, totalAdditions, totalDeletions, err
 }
 
 // GetDiffShortStat counts number of changed files, number of additions and deletions
-func GetDiffShortStat(ctx context.Context, repoPath string, args ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
+func GetDiffShortStat(ctx context.Context, repoPath string, args ...CmdArg) (numFiles, totalAdditions, totalDeletions int, err error) {
 	// Now if we call:
 	// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
 	// we get:
 	// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
-	args = append([]string{
+	args = append([]CmdArg{
 		"diff",
 		"--shortstat",
 	}, args...)
@@ -215,23 +215,23 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
 
 	numFiles, err = strconv.Atoi(groups[1])
 	if err != nil {
-		return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %v", stdout, err)
+		return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumFiles %w", stdout, err)
 	}
 
 	if len(groups[2]) != 0 {
 		totalAdditions, err = strconv.Atoi(groups[2])
 		if err != nil {
-			return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %v", stdout, err)
+			return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumAdditions %w", stdout, err)
 		}
 	}
 
 	if len(groups[3]) != 0 {
 		totalDeletions, err = strconv.Atoi(groups[3])
 		if err != nil {
-			return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %v", stdout, err)
+			return 0, 0, 0, fmt.Errorf("unable to parse shortstat: %s. Error parsing NumDeletions %w", stdout, err)
 		}
 	}
-	return
+	return numFiles, totalAdditions, totalDeletions, err
 }
 
 // GetDiffOrPatch generates either diff or formatted patch data between given revisions
@@ -247,7 +247,7 @@ func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, bi
 
 // GetDiff generates and returns patch data between given revisions, optimized for human readability
 func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
-	return NewCommand(repo.Ctx, "diff", "-p", base, head).Run(&RunOpts{
+	return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head).Run(&RunOpts{
 		Dir:    repo.Path,
 		Stdout: w,
 	})
@@ -255,7 +255,7 @@ func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
 
 // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
 func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
-	return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{
+	return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base, head).Run(&RunOpts{
 		Dir:    repo.Path,
 		Stdout: w,
 	})
@@ -264,14 +264,14 @@ func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
 // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
 func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
 	stderr := new(bytes.Buffer)
-	err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base+"..."+head).
+	err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base + "..." + head).
 		Run(&RunOpts{
 			Dir:    repo.Path,
 			Stdout: w,
 			Stderr: stderr,
 		})
 	if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
-		return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout", base, head).
+		return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base, head).
 			Run(&RunOpts{
 				Dir:    repo.Path,
 				Stdout: w,
@@ -282,7 +282,7 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
 
 // GetFilesChangedBetween returns a list of all files that have been changed between the given commits
 func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", base+".."+head).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
 	}
@@ -292,7 +292,7 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err
 // GetDiffFromMergeBase generates and return patch data from merge base to head
 func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
 	stderr := new(bytes.Buffer)
-	err := NewCommand(repo.Ctx, "diff", "-p", "--binary", base+"..."+head).
+	err := NewCommand(repo.Ctx, "diff", "-p", "--binary").AddDynamicArguments(base + "..." + head).
 		Run(&RunOpts{
 			Dir:    repo.Path,
 			Stdout: w,
diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go
index e163a3090bbe1..63f7254dea988 100644
--- a/modules/git/repo_compare_test.go
+++ b/modules/git/repo_compare_test.go
@@ -10,19 +10,16 @@ import (
 	"path/filepath"
 	"testing"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
 )
 
 func TestGetFormatPatch(t *testing.T) {
 	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
-	clonedPath, err := cloneRepo(bareRepo1Path, "repo1_TestGetFormatPatch")
+	clonedPath, err := cloneRepo(t, bareRepo1Path)
 	if err != nil {
 		assert.NoError(t, err)
 		return
 	}
-	defer util.RemoveAll(clonedPath)
 
 	repo, err := openRepositoryWithDefaultContext(clonedPath)
 	if err != nil {
@@ -84,12 +81,11 @@ func TestReadWritePullHead(t *testing.T) {
 	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 
 	// As we are writing we should clone the repository first
-	clonedPath, err := cloneRepo(bareRepo1Path, "TestReadWritePullHead")
+	clonedPath, err := cloneRepo(t, bareRepo1Path)
 	if err != nil {
 		assert.NoError(t, err)
 		return
 	}
-	defer util.RemoveAll(clonedPath)
 
 	repo, err := openRepositoryWithDefaultContext(clonedPath)
 	if err != nil {
@@ -117,8 +113,8 @@ func TestReadWritePullHead(t *testing.T) {
 		return
 	}
 
-	assert.Len(t, string(headContents), 40)
-	assert.True(t, string(headContents) == newCommit)
+	assert.Len(t, headContents, 40)
+	assert.True(t, headContents == newCommit)
 
 	// Remove file after the test
 	err = repo.RemoveReference(PullPrefix + "1/head")
diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go
index abbb349159d1a..25188d07e3088 100644
--- a/modules/git/repo_gpg.go
+++ b/modules/git/repo_gpg.go
@@ -18,7 +18,7 @@ func (gpgSettings *GPGSettings) LoadPublicKeyContent() error {
 		"gpg -a --export",
 		"gpg", "-a", "--export", gpgSettings.KeyID)
 	if err != nil {
-		return fmt.Errorf("Unable to get default signing key: %s, %s, %v", gpgSettings.KeyID, stderr, err)
+		return fmt.Errorf("Unable to get default signing key: %s, %s, %w", gpgSettings.KeyID, stderr, err)
 	}
 	gpgSettings.PublicKeyContent = content
 	return nil
diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go
index ae68dcaa87c1e..3ff761d9308a4 100644
--- a/modules/git/repo_index.go
+++ b/modules/git/repo_index.go
@@ -17,8 +17,8 @@ import (
 
 // ReadTreeToIndex reads a treeish to the index
 func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string) error {
-	if len(treeish) != 40 {
-		res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", treeish).RunStdString(&RunOpts{Dir: repo.Path})
+	if len(treeish) != SHAFullLength {
+		res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path})
 		if err != nil {
 			return err
 		}
@@ -38,7 +38,7 @@ func (repo *Repository) readTreeToIndex(id SHA1, indexFilename ...string) error
 	if len(indexFilename) > 0 {
 		env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
 	}
-	_, _, err := NewCommand(repo.Ctx, "read-tree", id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env})
+	_, _, err := NewCommand(repo.Ctx, "read-tree").AddDynamicArguments(id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env})
 	if err != nil {
 		return err
 	}
@@ -64,7 +64,7 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpD
 		defer cancel()
 		return "", "", func() {}, err
 	}
-	return
+	return filename, tmpDir, cancel, err
 }
 
 // EmptyIndex empties the index
@@ -75,12 +75,7 @@ func (repo *Repository) EmptyIndex() error {
 
 // LsFiles checks if the given filenames are in the index
 func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
-	cmd := NewCommand(repo.Ctx, "ls-files", "-z", "--")
-	for _, arg := range filenames {
-		if arg != "" {
-			cmd.AddArguments(arg)
-		}
-	}
+	cmd := NewCommand(repo.Ctx, "ls-files", "-z").AddDashesAndList(filenames...)
 	res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
@@ -116,7 +111,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
 
 // AddObjectToIndex adds the provided object hash to the index at the provided filename
 func (repo *Repository) AddObjectToIndex(mode string, object SHA1, filename string) error {
-	cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo", mode, object.String(), filename)
+	cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, object.String(), filename)
 	_, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index d237924f92a4c..7388ef403b923 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -57,7 +57,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 
 	tree := commit.Tree
 
-	entries, err := tree.ListEntriesRecursive()
+	entries, err := tree.ListEntriesRecursiveWithSize()
 	if err != nil {
 		return nil, err
 	}
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index c0c91c6fc618d..002e2525e2e8c 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -13,6 +13,8 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"code.gitea.io/gitea/modules/container"
 )
 
 // CodeActivityStats represents git statistics data
@@ -39,7 +41,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 
 	since := fromTime.Format(time.RFC3339)
 
-	stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since))).RunStdString(&RunOpts{Dir: repo.Path})
 	if runErr != nil {
 		return nil, runErr
 	}
@@ -59,15 +61,15 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 		_ = stdoutWriter.Close()
 	}()
 
-	args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
+	gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", CmdArg(fmt.Sprintf("--since='%s'", since)))
 	if len(branch) == 0 {
-		args = append(args, "--branches=*")
+		gitCmd.AddArguments("--branches=*")
 	} else {
-		args = append(args, "--first-parent", branch)
+		gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
 	}
 
 	stderr := new(strings.Builder)
-	err = NewCommand(repo.Ctx, args...).Run(&RunOpts{
+	err = gitCmd.Run(&RunOpts{
 		Env:    []string{},
 		Dir:    repo.Path,
 		Stdout: stdoutWriter,
@@ -80,7 +82,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 			stats.Additions = 0
 			stats.Deletions = 0
 			authors := make(map[string]*CodeActivityAuthor)
-			files := make(map[string]bool)
+			files := make(container.Set[string])
 			var author string
 			p := 0
 			for scanner.Scan() {
@@ -119,9 +121,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
 								stats.Deletions += c
 							}
 						}
-						if _, ok := files[parts[2]]; !ok {
-							files[parts[2]] = true
-						}
+						files.Add(parts[2])
 					}
 				}
 			}
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index 8444e8d035a0b..8585d824f91b1 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -25,13 +25,13 @@ func IsTagExist(ctx context.Context, repoPath, name string) bool {
 
 // CreateTag create one tag in the repository
 func (repo *Repository) CreateTag(name, revision string) error {
-	_, _, err := NewCommand(repo.Ctx, "tag", "--", name, revision).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "tag").AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
 // CreateAnnotatedTag create one annotated tag in the repository
 func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
-	_, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m", message, "--", name, revision).RunStdString(&RunOpts{Dir: repo.Path})
+	_, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
 	return err
 }
 
@@ -64,7 +64,7 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
 
 // GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
 func (repo *Repository) GetTagID(name string) (string, error) {
-	stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "--", name).RunStdString(&RunOpts{Dir: repo.Path})
+	stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return "", err
 	}
@@ -122,7 +122,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
 	rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr}
 
 	go func() {
-		err := NewCommand(repo.Ctx, "for-each-ref", "--format", forEachRefFmt.Flag(), "--sort", "-*creatordate", "refs/tags").Run(rc)
+		err := NewCommand(repo.Ctx, "for-each-ref", CmdArg("--format="+forEachRefFmt.Flag()), "--sort", "-*creatordate", "refs/tags").Run(rc)
 		if err != nil {
 			_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String()))
 		} else {
@@ -166,7 +166,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
 
 	tag.ID, err = NewIDFromString(ref["objectname"])
 	if err != nil {
-		return nil, fmt.Errorf("parse objectname '%s': %v", ref["objectname"], err)
+		return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err)
 	}
 
 	if tag.Type == "commit" {
@@ -176,7 +176,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) {
 		// annotated tag
 		tag.Object, err = NewIDFromString(ref["object"])
 		if err != nil {
-			return nil, fmt.Errorf("parse object '%s': %v", ref["object"], err)
+			return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err)
 		}
 	}
 
diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go
index 8d44db0a2e114..0bb0da21bf3b1 100644
--- a/modules/git/repo_tag_nogogit.go
+++ b/modules/git/repo_tag_nogogit.go
@@ -16,7 +16,7 @@ import (
 
 // IsTagExist returns true if given tag exists in the repository.
 func (repo *Repository) IsTagExist(name string) bool {
-	if name == "" {
+	if repo == nil || name == "" {
 		return false
 	}
 
@@ -26,8 +26,8 @@ func (repo *Repository) IsTagExist(name string) bool {
 // GetTags returns all tags of the repository.
 // returning at most limit tags, or all if limit is 0.
 func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
-	tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, "--tags", skip, limit)
-	return
+	tags, _, err = callShowRef(repo.Ctx, repo.Path, TagPrefix, []CmdArg{TagPrefix, "--sort=-taggerdate"}, skip, limit)
+	return tags, err
 }
 
 // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go
index 9d8467286205d..6a00473bb794a 100644
--- a/modules/git/repo_tag_test.go
+++ b/modules/git/repo_tag_test.go
@@ -8,8 +8,6 @@ import (
 	"path/filepath"
 	"testing"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -38,12 +36,11 @@ func TestRepository_GetTags(t *testing.T) {
 func TestRepository_GetTag(t *testing.T) {
 	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 
-	clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetTag")
+	clonedPath, err := cloneRepo(t, bareRepo1Path)
 	if err != nil {
 		assert.NoError(t, err)
 		return
 	}
-	defer util.RemoveAll(clonedPath)
 
 	bareRepo1, err := openRepositoryWithDefaultContext(clonedPath)
 	if err != nil {
@@ -143,12 +140,11 @@ func TestRepository_GetTag(t *testing.T) {
 func TestRepository_GetAnnotatedTag(t *testing.T) {
 	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 
-	clonedPath, err := cloneRepo(bareRepo1Path, "TestRepository_GetAnnotatedTag")
+	clonedPath, err := cloneRepo(t, bareRepo1Path)
 	if err != nil {
 		assert.NoError(t, err)
 		return
 	}
-	defer util.RemoveAll(clonedPath)
 
 	bareRepo1, err := openRepositoryWithDefaultContext(clonedPath)
 	if err != nil {
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 2ea3f0187a185..ba81bfc6dbbf1 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -35,10 +35,10 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
 		"GIT_COMMITTER_EMAIL="+committer.Email,
 		"GIT_COMMITTER_DATE="+commitTimeStr,
 	)
-	cmd := NewCommand(repo.Ctx, "commit-tree", tree.ID.String())
+	cmd := NewCommand(repo.Ctx, "commit-tree").AddDynamicArguments(tree.ID.String())
 
 	for _, parent := range opts.Parents {
-		cmd.AddArguments("-p", parent)
+		cmd.AddArguments("-p").AddDynamicArguments(parent)
 	}
 
 	messageBytes := new(bytes.Buffer)
@@ -46,7 +46,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
 	_, _ = messageBytes.WriteString("\n")
 
 	if opts.KeyID != "" || opts.AlwaysSign {
-		cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID))
+		cmd.AddArguments(CmdArg(fmt.Sprintf("-S%s", opts.KeyID)))
 	}
 
 	if opts.NoGPGSign {
diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go
index eef09cddd6a5b..9676bceebe6eb 100644
--- a/modules/git/repo_tree_gogit.go
+++ b/modules/git/repo_tree_gogit.go
@@ -20,8 +20,8 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 
 // GetTree find the tree object in the repository.
 func (repo *Repository) GetTree(idStr string) (*Tree, error) {
-	if len(idStr) != 40 {
-		res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify", idStr).RunStdString(&RunOpts{Dir: repo.Path})
+	if len(idStr) != SHAFullLength {
+		res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
 		if err != nil {
 			return nil, err
 		}
diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go
index dc4a5becb9dff..6dea6cf026492 100644
--- a/modules/git/repo_tree_nogogit.go
+++ b/modules/git/repo_tree_nogogit.go
@@ -67,7 +67,7 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) {
 
 // GetTree find the tree object in the repository.
 func (repo *Repository) GetTree(idStr string) (*Tree, error) {
-	if len(idStr) != 40 {
+	if len(idStr) != SHAFullLength {
 		res, err := repo.GetRefCommitID(idStr)
 		if err != nil {
 			return nil, err
diff --git a/modules/git/sha1.go b/modules/git/sha1.go
index 2da74733df725..7c777c5e47879 100644
--- a/modules/git/sha1.go
+++ b/modules/git/sha1.go
@@ -18,8 +18,16 @@ const EmptySHA = "0000000000000000000000000000000000000000"
 // EmptyTreeSHA is the SHA of an empty tree
 const EmptyTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
 
+// SHAFullLength is the full length of a git SHA
+const SHAFullLength = 40
+
 // SHAPattern can be used to determine if a string is an valid sha
-var SHAPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
+var shaPattern = regexp.MustCompile(`^[0-9a-f]{4,40}$`)
+
+// IsValidSHAPattern will check if the provided string matches the SHA Pattern
+func IsValidSHAPattern(sha string) bool {
+	return shaPattern.MatchString(sha)
+}
 
 // MustID always creates a new SHA1 from a [20]byte array with no validation of input.
 func MustID(b []byte) SHA1 {
@@ -46,7 +54,7 @@ func MustIDFromString(s string) SHA1 {
 func NewIDFromString(s string) (SHA1, error) {
 	var id SHA1
 	s = strings.TrimSpace(s)
-	if len(s) != 40 {
+	if len(s) != SHAFullLength {
 		return id, fmt.Errorf("Length must be 40: %s", s)
 	}
 	b, err := hex.DecodeString(s)
diff --git a/modules/git/sha1_nogogit.go b/modules/git/sha1_nogogit.go
index 1835c68f5a133..a2620cba6954c 100644
--- a/modules/git/sha1_nogogit.go
+++ b/modules/git/sha1_nogogit.go
@@ -58,5 +58,5 @@ func NewHasher(t ObjectType, size int64) Hasher {
 // Sum generates a SHA1 for the provided hash
 func (h Hasher) Sum() (sha1 SHA1) {
 	copy(sha1[:], h.Hash.Sum(nil))
-	return
+	return sha1
 }
diff --git a/modules/git/sha1_test.go b/modules/git/sha1_test.go
new file mode 100644
index 0000000000000..c5c00f5445ee3
--- /dev/null
+++ b/modules/git/sha1_test.go
@@ -0,0 +1,21 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package git
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsValidSHAPattern(t *testing.T) {
+	assert.True(t, IsValidSHAPattern("fee1"))
+	assert.True(t, IsValidSHAPattern("abc000"))
+	assert.True(t, IsValidSHAPattern("9023902390239023902390239023902390239023"))
+	assert.False(t, IsValidSHAPattern("90239023902390239023902390239023902390239023"))
+	assert.False(t, IsValidSHAPattern("abc"))
+	assert.False(t, IsValidSHAPattern("123g"))
+	assert.False(t, IsValidSHAPattern("some random text"))
+}
diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go
index fe81cd97df3b7..5ab38cd85252a 100644
--- a/modules/git/signature_gogit.go
+++ b/modules/git/signature_gogit.go
@@ -10,6 +10,7 @@ package git
 import (
 	"bytes"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/go-git/go-git/v5/plumbing/object"
@@ -19,8 +20,10 @@ import (
 type Signature = object.Signature
 
 // Helper to get a signature from the commit line, which looks like these:
-//     author Patrick Gundlach  1378823654 +0200
-//     author Patrick Gundlach  Thu, 07 Apr 2005 22:13:13 +0200
+//
+//	author Patrick Gundlach  1378823654 +0200
+//	author Patrick Gundlach  Thu, 07 Apr 2005 22:13:13 +0200
+//
 // but without the "author " at the beginning (this method should)
 // be used for author and committer.
 //
@@ -28,7 +31,9 @@ type Signature = object.Signature
 func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
 	sig := new(Signature)
 	emailStart := bytes.IndexByte(line, '<')
-	sig.Name = string(line[:emailStart-1])
+	if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
+		sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
+	}
 	emailEnd := bytes.IndexByte(line, '>')
 	sig.Email = string(line[emailStart+1 : emailEnd])
 
diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go
index 81da739a5b4da..3fa5c8da3e86e 100644
--- a/modules/git/signature_nogogit.go
+++ b/modules/git/signature_nogogit.go
@@ -11,6 +11,7 @@ import (
 	"bytes"
 	"fmt"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -37,8 +38,10 @@ func (s *Signature) Decode(b []byte) {
 }
 
 // Helper to get a signature from the commit line, which looks like these:
-//     author Patrick Gundlach  1378823654 +0200
-//     author Patrick Gundlach  Thu, 07 Apr 2005 22:13:13 +0200
+//
+//	author Patrick Gundlach  1378823654 +0200
+//	author Patrick Gundlach  Thu, 07 Apr 2005 22:13:13 +0200
+//
 // but without the "author " at the beginning (this method should)
 // be used for author and committer.
 func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
@@ -49,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
 		return
 	}
 
-	sig.Name = string(line[:emailStart-1])
+	if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
+		sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
+	}
 	sig.Email = string(line[emailStart+1 : emailEnd])
 
 	hasTime := emailEnd+2 < len(line)
@@ -91,5 +96,5 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
 			return
 		}
 	}
-	return
+	return sig, err
 }
diff --git a/modules/git/tree.go b/modules/git/tree.go
index a83336f3dbd35..f5944dd29c219 100644
--- a/modules/git/tree.go
+++ b/modules/git/tree.go
@@ -49,12 +49,9 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
 
 // LsTree checks if the given filenames are in the tree
 func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) {
-	cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only", "--", ref)
-	for _, arg := range filenames {
-		if arg != "" {
-			cmd.AddArguments(arg)
-		}
-	}
+	cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only").
+		AddDashesAndList(append([]string{ref}, filenames...)...)
+
 	res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
 	if err != nil {
 		return nil, err
diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go
index 54f8e140fbd06..480c5e44da7c6 100644
--- a/modules/git/tree_gogit.go
+++ b/modules/git/tree_gogit.go
@@ -57,8 +57,8 @@ func (t *Tree) ListEntries() (Entries, error) {
 	return entries, nil
 }
 
-// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
-func (t *Tree) ListEntriesRecursive() (Entries, error) {
+// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees
+func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
 	if t.gogitTree == nil {
 		err := t.loadTreeObject()
 		if err != nil {
@@ -92,3 +92,8 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
 
 	return entries, nil
 }
+
+// ListEntriesRecursiveFast is the alias of ListEntriesRecursiveWithSize for the gogit version
+func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
+	return t.ListEntriesRecursiveWithSize()
+}
diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go
index 7defb064a47df..5cbb5ffc94a84 100644
--- a/modules/git/tree_nogogit.go
+++ b/modules/git/tree_nogogit.go
@@ -80,7 +80,7 @@ func (t *Tree) ListEntries() (Entries, error) {
 		}
 	}
 
-	stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l", t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
+	stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
 	if runErr != nil {
 		if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") {
 			return nil, ErrNotExist{
@@ -99,13 +99,16 @@ func (t *Tree) ListEntries() (Entries, error) {
 	return t.entries, err
 }
 
-// ListEntriesRecursive returns all entries of current tree recursively including all subtrees
-func (t *Tree) ListEntriesRecursive() (Entries, error) {
+// listEntriesRecursive returns all entries of current tree recursively including all subtrees
+// extraArgs could be "-l" to get the size, which is slower
+func (t *Tree) listEntriesRecursive(extraArgs ...CmdArg) (Entries, error) {
 	if t.entriesRecursiveParsed {
 		return t.entriesRecursive, nil
 	}
 
-	stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-l", "-r", t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
+	args := append([]CmdArg{"ls-tree", "-t", "-r"}, extraArgs...)
+	args = append(args, CmdArg(t.ID.String()))
+	stdout, _, runErr := NewCommand(t.repo.Ctx, args...).RunStdBytes(&RunOpts{Dir: t.repo.Path})
 	if runErr != nil {
 		return nil, runErr
 	}
@@ -118,3 +121,13 @@ func (t *Tree) ListEntriesRecursive() (Entries, error) {
 
 	return t.entriesRecursive, err
 }
+
+// ListEntriesRecursiveFast returns all entries of current tree recursively including all subtrees, no size
+func (t *Tree) ListEntriesRecursiveFast() (Entries, error) {
+	return t.listEntriesRecursive()
+}
+
+// ListEntriesRecursiveWithSize returns all entries of current tree recursively including all subtrees, with size
+func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
+	return t.listEntriesRecursive("--long")
+}
diff --git a/modules/git/utils.go b/modules/git/utils.go
index 53c124ac8a2b2..d6bf9f4413cd5 100644
--- a/modules/git/utils.go
+++ b/modules/git/utils.go
@@ -163,7 +163,7 @@ func (l *LimitedReaderCloser) Read(p []byte) (n int, err error) {
 	}
 	n, err = l.R.Read(p)
 	l.N -= int64(n)
-	return
+	return n, err
 }
 
 // Close implements io.Closer
diff --git a/modules/gitgraph/graph.go b/modules/gitgraph/graph.go
index 271382525a4a5..d6342c928006c 100644
--- a/modules/gitgraph/graph.go
+++ b/modules/gitgraph/graph.go
@@ -24,35 +24,29 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
 		page = 1
 	}
 
-	args := make([]string, 0, 12+len(branches)+len(files))
-
-	args = append(args, "--graph", "--date-order", "--decorate=full")
+	graphCmd := git.NewCommand(r.Ctx, "log", "--graph", "--date-order", "--decorate=full")
 
 	if hidePRRefs {
-		args = append(args, "--exclude="+git.PullPrefix+"*")
+		graphCmd.AddArguments("--exclude=" + git.PullPrefix + "*")
 	}
 
 	if len(branches) == 0 {
-		args = append(args, "--all")
+		graphCmd.AddArguments("--all")
 	}
 
-	args = append(args,
+	graphCmd.AddArguments(
 		"-C",
 		"-M",
-		fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
+		git.CmdArg(fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page)),
 		"--date=iso",
-		fmt.Sprintf("--pretty=format:%s", format))
+		git.CmdArg(fmt.Sprintf("--pretty=format:%s", format)))
 
 	if len(branches) > 0 {
-		args = append(args, branches...)
+		graphCmd.AddDynamicArguments(branches...)
 	}
-	args = append(args, "--")
 	if len(files) > 0 {
-		args = append(args, files...)
+		graphCmd.AddDashesAndList(files...)
 	}
-
-	graphCmd := git.NewCommand(r.Ctx, "log")
-	graphCmd.AddArguments(args...)
 	graph := NewGraph()
 
 	stderr := new(strings.Builder)
diff --git a/modules/gitgraph/graph_test.go b/modules/gitgraph/graph_test.go
index ea6553529a293..2cfbe4b2fa6b4 100644
--- a/modules/gitgraph/graph_test.go
+++ b/modules/gitgraph/graph_test.go
@@ -53,7 +53,7 @@ func BenchmarkParseGlyphs(b *testing.B) {
 	parser := &Parser{}
 	parser.Reset()
 	tgBytes := []byte(testglyphs)
-	tg := tgBytes
+	var tg []byte
 	for i := 0; i < b.N; i++ {
 		parser.Reset()
 		tg = tgBytes
diff --git a/modules/graceful/context.go b/modules/graceful/context.go
index 9d955329a42b9..b9d975a1d50a4 100644
--- a/modules/graceful/context.go
+++ b/modules/graceful/context.go
@@ -26,7 +26,7 @@ func NewChannelContext(done <-chan struct{}, err error) *ChannelContext {
 // Deadline returns the time when work done on behalf of this context
 // should be canceled. There is no Deadline for a ChannelContext
 func (ctx *ChannelContext) Deadline() (deadline time.Time, ok bool) {
-	return
+	return deadline, ok
 }
 
 // Done returns the channel provided at the creation of this context.
diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go
index 8766cfca0efb4..21f019fb56f8c 100644
--- a/modules/graceful/manager.go
+++ b/modules/graceful/manager.go
@@ -24,11 +24,12 @@ const (
 	stateTerminate
 )
 
-// There are three places that could inherit sockets:
+// There are some places that could inherit sockets:
 //
 // * HTTP or HTTPS main listener
+// * HTTP or HTTPS install listener
 // * HTTP redirection fallback
-// * SSH
+// * Builtin SSH listener
 //
 // If you add an additional place you must increment this number
 // and add a function to call manager.InformCleanup if it's not going to be used
@@ -305,8 +306,9 @@ func (g *Manager) setState(st state) {
 	g.state = st
 }
 
-// InformCleanup tells the cleanup wait group that we have either taken a listener
-// or will not be taking a listener
+// InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener.
+// At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init,
+// so this function MUST be called if a server is not used.
 func (g *Manager) InformCleanup() {
 	g.createServerWaitGroup.Done()
 }
diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go
index e7e619f53f617..10c1d67b97204 100644
--- a/modules/graceful/manager_windows.go
+++ b/modules/graceful/manager_windows.go
@@ -114,9 +114,9 @@ func (g *Manager) start() {
 // Execute makes Manager implement svc.Handler
 func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
 	if setting.StartupTimeout > 0 {
-		status <- svc.Status{State: svc.StartPending}
-	} else {
 		status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
+	} else {
+		status <- svc.Status{State: svc.StartPending}
 	}
 
 	log.Trace("Awaiting server start-up")
diff --git a/modules/graceful/net_unix.go b/modules/graceful/net_unix.go
index 680ff529af885..0bb589e231386 100644
--- a/modules/graceful/net_unix.go
+++ b/modules/graceful/net_unix.go
@@ -23,6 +23,7 @@ import (
 const (
 	listenFDs = "LISTEN_FDS"
 	startFD   = 3
+	unlinkFDs = "GITEA_UNLINK_FDS"
 )
 
 // In order to keep the working directory the same as when we started we record
@@ -33,8 +34,10 @@ var (
 	once  = sync.Once{}
 	mutex = sync.Mutex{}
 
-	providedListeners = []net.Listener{}
-	activeListeners   = []net.Listener{}
+	providedListenersToUnlink = []bool{}
+	activeListenersToUnlink   = []bool{}
+	providedListeners         = []net.Listener{}
+	activeListeners           = []net.Listener{}
 )
 
 func getProvidedFDs() (savedErr error) {
@@ -49,10 +52,20 @@ func getProvidedFDs() (savedErr error) {
 		}
 		n, err := strconv.Atoi(numFDs)
 		if err != nil {
-			savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDs, numFDs, err)
+			savedErr = fmt.Errorf("%s is not a number: %s. Err: %w", listenFDs, numFDs, err)
 			return
 		}
 
+		fdsToUnlinkStr := strings.Split(os.Getenv(unlinkFDs), ",")
+		providedListenersToUnlink = make([]bool, n)
+		for _, fdStr := range fdsToUnlinkStr {
+			i, err := strconv.Atoi(fdStr)
+			if err != nil || i < 0 || i >= n {
+				continue
+			}
+			providedListenersToUnlink[i] = true
+		}
+
 		for i := startFD; i < n+startFD; i++ {
 			file := os.NewFile(uintptr(i), fmt.Sprintf("listener_FD%d", i))
 
@@ -68,7 +81,7 @@ func getProvidedFDs() (savedErr error) {
 			}
 
 			// If needed we can handle packetconns here.
-			savedErr = fmt.Errorf("Error getting provided socket fd %d: %v", i, err)
+			savedErr = fmt.Errorf("Error getting provided socket fd %d: %w", i, err)
 			return
 		}
 	})
@@ -85,7 +98,7 @@ func CloseProvidedListeners() error {
 		if err != nil {
 			log.Error("Error in closing unused provided listener: %v", err)
 			if returnableError != nil {
-				returnableError = fmt.Errorf("%v & %v", returnableError, err)
+				returnableError = fmt.Errorf("%v & %w", returnableError, err)
 			} else {
 				returnableError = err
 			}
@@ -136,8 +149,11 @@ func GetListenerTCP(network string, address *net.TCPAddr) (*net.TCPListener, err
 	for i, l := range providedListeners {
 		if isSameAddr(l.Addr(), address) {
 			providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
+			needsUnlink := providedListenersToUnlink[i]
+			providedListenersToUnlink = append(providedListenersToUnlink[:i], providedListenersToUnlink[i+1:]...)
 
 			activeListeners = append(activeListeners, l)
+			activeListenersToUnlink = append(activeListenersToUnlink, needsUnlink)
 			return l.(*net.TCPListener), nil
 		}
 	}
@@ -148,6 +164,7 @@ func GetListenerTCP(network string, address *net.TCPAddr) (*net.TCPListener, err
 		return nil, err
 	}
 	activeListeners = append(activeListeners, l)
+	activeListenersToUnlink = append(activeListenersToUnlink, false)
 	return l, nil
 }
 
@@ -166,16 +183,22 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
 	for i, l := range providedListeners {
 		if isSameAddr(l.Addr(), address) {
 			providedListeners = append(providedListeners[:i], providedListeners[i+1:]...)
+			needsUnlink := providedListenersToUnlink[i]
+			providedListenersToUnlink = append(providedListenersToUnlink[:i], providedListenersToUnlink[i+1:]...)
+
+			activeListenersToUnlink = append(activeListenersToUnlink, needsUnlink)
 			activeListeners = append(activeListeners, l)
 			unixListener := l.(*net.UnixListener)
-			unixListener.SetUnlinkOnClose(true)
+			if needsUnlink {
+				unixListener.SetUnlinkOnClose(true)
+			}
 			return unixListener, nil
 		}
 	}
 
 	// make a fresh listener
 	if err := util.Remove(address.Name); err != nil && !os.IsNotExist(err) {
-		return nil, fmt.Errorf("Failed to remove unix socket %s: %v", address.Name, err)
+		return nil, fmt.Errorf("Failed to remove unix socket %s: %w", address.Name, err)
 	}
 
 	l, err := net.ListenUnix(network, address)
@@ -185,10 +208,11 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
 
 	fileMode := os.FileMode(setting.UnixSocketPermission)
 	if err = os.Chmod(address.Name, fileMode); err != nil {
-		return nil, fmt.Errorf("Failed to set permission of unix socket to %s: %v", fileMode.String(), err)
+		return nil, fmt.Errorf("Failed to set permission of unix socket to %s: %w", fileMode.String(), err)
 	}
 
 	activeListeners = append(activeListeners, l)
+	activeListenersToUnlink = append(activeListenersToUnlink, true)
 	return l, nil
 }
 
@@ -223,3 +247,11 @@ func getActiveListeners() []net.Listener {
 	copy(listeners, activeListeners)
 	return listeners
 }
+
+func getActiveListenersToUnlink() []bool {
+	mutex.Lock()
+	defer mutex.Unlock()
+	listenersToUnlink := make([]bool, len(activeListenersToUnlink))
+	copy(listenersToUnlink, activeListenersToUnlink)
+	return listenersToUnlink
+}
diff --git a/modules/graceful/restart_unix.go b/modules/graceful/restart_unix.go
index 2654ddfb94d84..1d0d1059e9fd1 100644
--- a/modules/graceful/restart_unix.go
+++ b/modules/graceful/restart_unix.go
@@ -12,6 +12,7 @@ import (
 	"net"
 	"os"
 	"os/exec"
+	"strconv"
 	"strings"
 	"sync"
 	"syscall"
@@ -75,6 +76,20 @@ func RestartProcess() (int, error) {
 	}
 	env = append(env, fmt.Sprintf("%s=%d", listenFDs, len(listeners)))
 
+	sb := &strings.Builder{}
+	for i, unlink := range getActiveListenersToUnlink() {
+		if !unlink {
+			continue
+		}
+		_, _ = sb.WriteString(strconv.Itoa(i))
+		_, _ = sb.WriteString(",")
+	}
+	unlinkStr := sb.String()
+	if len(unlinkStr) > 0 {
+		unlinkStr = unlinkStr[:len(unlinkStr)-1]
+		env = append(env, fmt.Sprintf("%s=%s", unlinkFDs, unlinkStr))
+	}
+
 	allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
 	process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
 		Dir:   originalWD,
diff --git a/modules/graceful/server.go b/modules/graceful/server.go
index 159a9879df2f9..30a460a943c51 100644
--- a/modules/graceful/server.go
+++ b/modules/graceful/server.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/proxyprotocol"
 	"code.gitea.io/gitea/modules/setting"
 )
 
@@ -79,16 +80,27 @@ func NewServer(network, address, name string) *Server {
 
 // ListenAndServe listens on the provided network address and then calls Serve
 // to handle requests on incoming connections.
-func (srv *Server) ListenAndServe(serve ServeFunction) error {
+func (srv *Server) ListenAndServe(serve ServeFunction, useProxyProtocol bool) error {
 	go srv.awaitShutdown()
 
-	l, err := GetListener(srv.network, srv.address)
+	listener, err := GetListener(srv.network, srv.address)
 	if err != nil {
 		log.Error("Unable to GetListener: %v", err)
 		return err
 	}
 
-	srv.listener = newWrappedListener(l, srv)
+	// we need to wrap the listener to take account of our lifecycle
+	listener = newWrappedListener(listener, srv)
+
+	// Now we need to take account of ProxyProtocol settings...
+	if useProxyProtocol {
+		listener = &proxyprotocol.Listener{
+			Listener:           listener,
+			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
+			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
+		}
+	}
+	srv.listener = listener
 
 	srv.BeforeBegin(srv.network, srv.address)
 
@@ -97,22 +109,44 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
 
 // ListenAndServeTLSConfig listens on the provided network address and then calls
 // Serve to handle requests on incoming TLS connections.
-func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
+func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 	go srv.awaitShutdown()
 
 	if tlsConfig.MinVersion == 0 {
 		tlsConfig.MinVersion = tls.VersionTLS12
 	}
 
-	l, err := GetListener(srv.network, srv.address)
+	listener, err := GetListener(srv.network, srv.address)
 	if err != nil {
 		log.Error("Unable to get Listener: %v", err)
 		return err
 	}
 
-	wl := newWrappedListener(l, srv)
-	srv.listener = tls.NewListener(wl, tlsConfig)
+	// we need to wrap the listener to take account of our lifecycle
+	listener = newWrappedListener(listener, srv)
+
+	// Now we need to take account of ProxyProtocol settings... If we're not bridging then we expect that the proxy will forward the connection to us
+	if useProxyProtocol && !proxyProtocolTLSBridging {
+		listener = &proxyprotocol.Listener{
+			Listener:           listener,
+			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
+			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
+		}
+	}
+
+	// Now handle the tls protocol
+	listener = tls.NewListener(listener, tlsConfig)
+
+	// Now if we're bridging then we need the proxy to tell us who we're bridging for...
+	if useProxyProtocol && proxyProtocolTLSBridging {
+		listener = &proxyprotocol.Listener{
+			Listener:           listener,
+			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
+			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
+		}
+	}
 
+	srv.listener = listener
 	srv.BeforeBegin(srv.network, srv.address)
 
 	return srv.Serve(serve)
diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go
index f7b22ceb5e0a7..8ab2bdf41ff9a 100644
--- a/modules/graceful/server_http.go
+++ b/modules/graceful/server_http.go
@@ -28,14 +28,14 @@ func newHTTPServer(network, address, name string, handler http.Handler) (*Server
 
 // HTTPListenAndServe listens on the provided network address and then calls Serve
 // to handle requests on incoming connections.
-func HTTPListenAndServe(network, address, name string, handler http.Handler) error {
+func HTTPListenAndServe(network, address, name string, handler http.Handler, useProxyProtocol bool) error {
 	server, lHandler := newHTTPServer(network, address, name, handler)
-	return server.ListenAndServe(lHandler)
+	return server.ListenAndServe(lHandler, useProxyProtocol)
 }
 
 // HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve
 // to handle requests on incoming connections.
-func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error {
+func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 	server, lHandler := newHTTPServer(network, address, name, handler)
-	return server.ListenAndServeTLSConfig(tlsConfig, lHandler)
+	return server.ListenAndServeTLSConfig(tlsConfig, lHandler, useProxyProtocol, proxyProtocolTLSBridging)
 }
diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go
index a72f26d5f0755..65ed74b019950 100644
--- a/modules/highlight/highlight.go
+++ b/modules/highlight/highlight.go
@@ -10,6 +10,7 @@ import (
 	"bytes"
 	"fmt"
 	gohtml "html"
+	"io"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -18,15 +19,15 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 
-	"github.com/alecthomas/chroma"
-	"github.com/alecthomas/chroma/formatters/html"
-	"github.com/alecthomas/chroma/lexers"
-	"github.com/alecthomas/chroma/styles"
+	"github.com/alecthomas/chroma/v2"
+	"github.com/alecthomas/chroma/v2/formatters/html"
+	"github.com/alecthomas/chroma/v2/lexers"
+	"github.com/alecthomas/chroma/v2/styles"
 	lru "github.com/hashicorp/golang-lru"
 )
 
 // don't index files larger than this many bytes for performance purposes
-const sizeLimit = 1000000
+const sizeLimit = 1024 * 1024
 
 var (
 	// For custom user mapping
@@ -40,11 +41,12 @@ var (
 // NewContext loads custom highlight map from local config
 func NewContext() {
 	once.Do(func() {
-		keys := setting.Cfg.Section("highlight.mapping").Keys()
-		for i := range keys {
-			highlightMapping[keys[i].Name()] = keys[i].Value()
+		if setting.Cfg != nil {
+			keys := setting.Cfg.Section("highlight.mapping").Keys()
+			for i := range keys {
+				highlightMapping[keys[i].Name()] = keys[i].Value()
+			}
 		}
-
 		// The size 512 is simply a conservative rule of thumb
 		c, err := lru.New2Q(512)
 		if err != nil {
@@ -58,7 +60,7 @@ func NewContext() {
 func Code(fileName, language, code string) string {
 	NewContext()
 
-	// diff view newline will be passed as empty, change to literal \n so it can be copied
+	// diff view newline will be passed as empty, change to literal '\n' so it can be copied
 	// preserve literal newline in blame view
 	if code == "" || code == "\n" {
 		return "\n"
@@ -114,7 +116,7 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string {
 	htmlbuf := bytes.Buffer{}
 	htmlw := bufio.NewWriter(&htmlbuf)
 
-	iterator, err := lexer.Tokenise(nil, string(code))
+	iterator, err := lexer.Tokenise(nil, code)
 	if err != nil {
 		log.Error("Can't tokenize code: %v", err)
 		return code
@@ -126,36 +128,29 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string {
 		return code
 	}
 
-	htmlw.Flush()
+	_ = htmlw.Flush()
 	// Chroma will add newlines for certain lexers in order to highlight them properly
-	// Once highlighted, strip them here so they don't cause copy/paste trouble in HTML output
+	// Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
 	return strings.TrimSuffix(htmlbuf.String(), "\n")
 }
 
-// File returns a slice of chroma syntax highlighted lines of code
-func File(numLines int, fileName, language string, code []byte) []string {
+// File returns a slice of chroma syntax highlighted HTML lines of code
+func File(fileName, language string, code []byte) ([]string, error) {
 	NewContext()
 
 	if len(code) > sizeLimit {
-		return plainText(string(code), numLines)
+		return PlainText(code), nil
 	}
+
 	formatter := html.New(html.WithClasses(true),
 		html.WithLineNumbers(false),
 		html.PreventSurroundingPre(true),
 	)
 
-	if formatter == nil {
-		log.Error("Couldn't create chroma formatter")
-		return plainText(string(code), numLines)
-	}
-
-	htmlbuf := bytes.Buffer{}
-	htmlw := bufio.NewWriter(&htmlbuf)
-
 	var lexer chroma.Lexer
 
 	// provided language overrides everything
-	if len(language) > 0 {
+	if language != "" {
 		lexer = lexers.Get(language)
 	}
 
@@ -166,9 +161,9 @@ func File(numLines int, fileName, language string, code []byte) []string {
 	}
 
 	if lexer == nil {
-		language := analyze.GetCodeLanguage(fileName, code)
+		guessLanguage := analyze.GetCodeLanguage(fileName, code)
 
-		lexer = lexers.Get(language)
+		lexer = lexers.Get(guessLanguage)
 		if lexer == nil {
 			lexer = lexers.Match(fileName)
 			if lexer == nil {
@@ -179,54 +174,41 @@ func File(numLines int, fileName, language string, code []byte) []string {
 
 	iterator, err := lexer.Tokenise(nil, string(code))
 	if err != nil {
-		log.Error("Can't tokenize code: %v", err)
-		return plainText(string(code), numLines)
+		return nil, fmt.Errorf("can't tokenize code: %w", err)
 	}
 
-	err = formatter.Format(htmlw, styles.GitHub, iterator)
-	if err != nil {
-		log.Error("Can't format code: %v", err)
-		return plainText(string(code), numLines)
-	}
+	tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
+	htmlBuf := &bytes.Buffer{}
 
-	htmlw.Flush()
-	finalNewLine := false
-	if len(code) > 0 {
-		finalNewLine = code[len(code)-1] == '\n'
-	}
-
-	m := make([]string, 0, numLines)
-	for _, v := range strings.SplitN(htmlbuf.String(), "\n", numLines) {
-		content := string(v)
-		// need to keep lines that are only \n so copy/paste works properly in browser
-		if content == "" {
-			content = "\n"
-		} else if content == `` {
-			content += "\n "
-		} else if content == `` {
-			content += "\n"
+	lines := make([]string, 0, len(tokensLines))
+	for _, tokens := range tokensLines {
+		iterator = chroma.Literator(tokens...)
+		err = formatter.Format(htmlBuf, styles.GitHub, iterator)
+		if err != nil {
+			return nil, fmt.Errorf("can't format code: %w", err)
 		}
-		content = strings.TrimSuffix(content, ``)
-		content = strings.TrimPrefix(content, ` `)
-		m = append(m, content)
-	}
-	if finalNewLine {
-		m = append(m, "\n ")
+		lines = append(lines, htmlBuf.String())
+		htmlBuf.Reset()
 	}
 
-	return m
+	return lines, nil
 }
 
-// return unhiglighted map
-func plainText(code string, numLines int) []string {
-	m := make([]string, 0, numLines)
-	for _, v := range strings.SplitN(string(code), "\n", numLines) {
-		content := string(v)
-		// need to keep lines that are only \n so copy/paste works properly in browser
-		if content == "" {
-			content = "\n"
+// PlainText returns non-highlighted HTML for code
+func PlainText(code []byte) []string {
+	r := bufio.NewReader(bytes.NewReader(code))
+	m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1)
+	for {
+		content, err := r.ReadString('\n')
+		if err != nil && err != io.EOF {
+			log.Error("failed to read string from buffer: %v", err)
+			break
+		}
+		if content == "" && err == io.EOF {
+			break
 		}
-		m = append(m, gohtml.EscapeString(content))
+		s := gohtml.EscapeString(content)
+		m = append(m, s)
 	}
 	return m
 }
diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go
index e5dfedd2b3c8e..8f83f4a2f6128 100644
--- a/modules/highlight/highlight_test.go
+++ b/modules/highlight/highlight_test.go
@@ -8,97 +8,146 @@ import (
 	"strings"
 	"testing"
 
-	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/ini.v1"
 )
 
+func lines(s string) []string {
+	return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n")
+}
+
 func TestFile(t *testing.T) {
-	setting.Cfg = ini.Empty()
 	tests := []struct {
-		name     string
-		numLines int
-		fileName string
-		code     string
-		want     string
+		name string
+		code string
+		want []string
 	}{
 		{
-			name:     ".drone.yml",
-			numLines: 12,
-			fileName: ".drone.yml",
-			code: util.Dedent(`
-				kind: pipeline
-				name: default
+			name: "empty.py",
+			code: "",
+			want: lines(""),
+		},
+		{
+			name: "tags.txt",
+			code: "<>",
+			want: lines("<>"),
+		},
+		{
+			name: "tags.py",
+			code: "<>",
+			want: lines(`< > `),
+		},
+		{
+			name: "eol-no.py",
+			code: "a=1",
+			want: lines(`a = 1 `),
+		},
+		{
+			name: "eol-newline1.py",
+			code: "a=1\n",
+			want: lines(`a = 1 \n`),
+		},
+		{
+			name: "eol-newline2.py",
+			code: "a=1\n\n",
+			want: lines(`
+a = 1 \n
+\n
+			`,
+			),
+		},
+		{
+			name: "empty-line-with-space.py",
+			code: strings.ReplaceAll(strings.TrimSpace(`
+def:
+    a=1
 
-				steps:
-				- name: test
-					image: golang:1.13
-					environment:
-						GOPROXY: https://goproxy.cn
-					commands:
-					- go get -u
-					- go build -v
-					- go test -v -race -coverprofile=coverage.txt -covermode=atomic
-			`),
-			want: util.Dedent(`
-				kind :   pipeline 
-				name :   default 
-				
-				 steps : 
-				name :   test 
-					 image :   golang:1.13 
-					 environment : 
-						 GOPROXY :   https://goproxy.cn 
-					 commands : 
-					 - go get -u 
-					 - go build -v 
-					 - go test -v -race -coverprofile=coverage.txt -covermode=atomic def : \n
+    a = 1 \n
+\n
+b = ' ' \n
+    \n
+c = 2 `,
+			),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			out, err := File(tt.name, "", []byte(tt.code))
+			assert.NoError(t, err)
+			expected := strings.Join(tt.want, "\n")
+			actual := strings.Join(out, "\n")
+			assert.Equal(t, strings.Count(actual, ""))
+			assert.EqualValues(t, expected, actual)
+		})
+	}
+}
+
+func TestPlainText(t *testing.T) {
+	tests := []struct {
+		name string
+		code string
+		want []string
+	}{
+		{
+			name: "empty.py",
+			code: "",
+			want: lines(""),
+		},
+		{
+			name: "tags.py",
+			code: "<>",
+			want: lines("<>"),
+		},
+		{
+			name: "eol-no.py",
+			code: "a=1",
+			want: lines(`a=1`),
+		},
+		{
+			name: "eol-newline1.py",
+			code: "a=1\n",
+			want: lines(`a=1\n`),
+		},
+		{
+			name: "eol-newline2.py",
+			code: "a=1\n\n",
+			want: lines(`
+a=1\n
+\n
 			`),
 		},
 		{
-			name:     ".drone.yml - trailing space",
-			numLines: 13,
-			fileName: ".drone.yml",
-			code: strings.Replace(util.Dedent(`
-				kind: pipeline
-				name: default
+			name: "empty-line-with-space.py",
+			code: strings.ReplaceAll(strings.TrimSpace(`
+def:
+    a=1
 
-				steps:
-				- name: test
-					image: golang:1.13
-					environment:
-						GOPROXY: https://goproxy.cn
-					commands:
-					- go get -u
-					- go build -v
-					- go test -v -race -coverprofile=coverage.txt -covermode=atomic
-			`)+"\n", "name: default", "name: default  ", 1),
-			want: util.Dedent(`
-				kind :   pipeline 
-				name :   default   
-				
-				 steps : 
-				name :   test 
-					 image :   golang:1.13 
-					 environment : 
-						 GOPROXY :   https://goproxy.cn 
-					 commands : 
-					 - go get -u 
-					 - go build -v 
-					 - go test -v -race -coverprofile=coverage.txt -covermode=atomic 
-				
-				 
-			`),
+b=''
+{space}
+c=2
+			`), "{space}", "    "),
+			want: lines(`
+def:\n
+    a=1\n
+\n
+b=''\n
+    \n
+c=2`),
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := strings.Join(File(tt.numLines, tt.fileName, "", []byte(tt.code)), "\n")
-			assert.Equal(t, tt.want, got)
+			out := PlainText([]byte(tt.code))
+			expected := strings.Join(tt.want, "\n")
+			actual := strings.Join(out, "\n")
+			assert.EqualValues(t, expected, actual)
 		})
 	}
 }
diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go
index 00bbc6cb0a83f..a092e07f411a4 100644
--- a/modules/hostmatcher/hostmatcher.go
+++ b/modules/hostmatcher/hostmatcher.go
@@ -78,6 +78,11 @@ func (hl *HostMatchList) AppendBuiltin(builtin string) {
 	hl.builtins = append(hl.builtins, builtin)
 }
 
+// AppendPattern appends more pattern to match
+func (hl *HostMatchList) AppendPattern(pattern string) {
+	hl.patterns = append(hl.patterns, pattern)
+}
+
 // IsEmpty checks if the checklist is empty
 func (hl *HostMatchList) IsEmpty() bool {
 	return hl == nil || (len(hl.builtins) == 0 && len(hl.patterns) == 0 && len(hl.ipNets) == 0)
@@ -125,14 +130,14 @@ func (hl *HostMatchList) checkIP(ip net.IP) bool {
 
 // MatchHostName checks if the host matches an allow/deny(block) list
 func (hl *HostMatchList) MatchHostName(host string) bool {
+	if hl == nil {
+		return false
+	}
+
 	hostname, _, err := net.SplitHostPort(host)
 	if err != nil {
 		hostname = host
 	}
-
-	if hl == nil {
-		return false
-	}
 	if hl.checkPattern(hostname) {
 		return true
 	}
diff --git a/modules/hostmatcher/http.go b/modules/hostmatcher/http.go
index 31430a9595898..84cd2974ec8e4 100644
--- a/modules/hostmatcher/http.go
+++ b/modules/hostmatcher/http.go
@@ -35,7 +35,7 @@ func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx
 				// in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here
 				tcpAddr, err := net.ResolveTCPAddr(network, ipAddr)
 				if err != nil {
-					return fmt.Errorf("%s can only call HTTP servers via TCP, deny '%s(%s:%s)', err=%v", usage, host, network, ipAddr, err)
+					return fmt.Errorf("%s can only call HTTP servers via TCP, deny '%s(%s:%s)', err=%w", usage, host, network, ipAddr, err)
 				}
 
 				var blockedError error
diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go
index 5797e981cf80f..750233d4a71c2 100644
--- a/modules/httpcache/httpcache.go
+++ b/modules/httpcache/httpcache.go
@@ -17,16 +17,23 @@ import (
 )
 
 // AddCacheControlToHeader adds suitable cache-control headers to response
-func AddCacheControlToHeader(h http.Header, d time.Duration) {
+func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
+	directives := make([]string, 0, 2+len(additionalDirectives))
+
 	if setting.IsProd {
-		h.Set("Cache-Control", "private, max-age="+strconv.Itoa(int(d.Seconds())))
+		if maxAge == 0 {
+			directives = append(directives, "no-store")
+		} else {
+			directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
+		}
 	} else {
-		h.Set("Cache-Control", "no-store")
+		directives = append(directives, "no-store")
+
 		// to remind users they are using non-prod setting.
-		// some users may be confused by "Cache-Control: no-store" in their setup if they did wrong to `RUN_MODE` in `app.ini`.
 		h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
-		h.Add("X-Gitea-Debug", "CacheControl=no-store")
 	}
+
+	h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
 }
 
 // generateETag generates an ETag based on size, filename and file modification time
diff --git a/modules/indexer/code/bleve.go b/modules/indexer/code/bleve.go
index 1abb3c0219ad5..3ea1c86178482 100644
--- a/modules/indexer/code/bleve.go
+++ b/modules/indexer/code/bleve.go
@@ -194,12 +194,12 @@ func (b *BleveIndexer) addUpdate(ctx context.Context, batchWriter git.WriteClose
 	var err error
 	if !update.Sized {
 		var stdout string
-		stdout, _, err = git.NewCommand(ctx, "cat-file", "-s", update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
+		stdout, _, err = git.NewCommand(ctx, "cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
 		if err != nil {
 			return err
 		}
 		if size, err = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64); err != nil {
-			return fmt.Errorf("Misformatted git cat-file output: %v", err)
+			return fmt.Errorf("Misformatted git cat-file output: %w", err)
 		}
 	}
 
@@ -392,7 +392,7 @@ func (b *BleveIndexer) Search(ctx context.Context, repoIDs []int64, language, ke
 
 	searchResults := make([]*SearchResult, len(result.Hits))
 	for i, hit := range result.Hits {
-		var startIndex, endIndex int = -1, -1
+		startIndex, endIndex := -1, -1
 		for _, locations := range hit.Locations["Content"] {
 			location := locations[0]
 			locationStart := int(location.Start)
diff --git a/modules/indexer/code/bleve_test.go b/modules/indexer/code/bleve_test.go
index 37ed2eb9f021c..a34d54bc0e19a 100644
--- a/modules/indexer/code/bleve_test.go
+++ b/modules/indexer/code/bleve_test.go
@@ -5,11 +5,9 @@
 package code
 
 import (
-	"os"
 	"testing"
 
 	"code.gitea.io/gitea/models/unittest"
-	"code.gitea.io/gitea/modules/util"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -17,13 +15,7 @@ import (
 func TestBleveIndexAndSearch(t *testing.T) {
 	unittest.PrepareTestEnv(t)
 
-	dir, err := os.MkdirTemp("", "bleve.index")
-	assert.NoError(t, err)
-	if err != nil {
-		assert.Fail(t, "Unable to create temporary directory")
-		return
-	}
-	defer util.RemoveAll(dir)
+	dir := t.TempDir()
 
 	idx, _, err := NewBleveIndexer(dir)
 	if err != nil {
diff --git a/modules/indexer/code/elastic_search.go b/modules/indexer/code/elastic_search.go
index 7263f27657c8e..dd3c9c9771410 100644
--- a/modules/indexer/code/elastic_search.go
+++ b/modules/indexer/code/elastic_search.go
@@ -223,12 +223,12 @@ func (b *ElasticSearchIndexer) addUpdate(ctx context.Context, batchWriter git.Wr
 	var err error
 	if !update.Sized {
 		var stdout string
-		stdout, _, err = git.NewCommand(ctx, "cat-file", "-s", update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
+		stdout, _, err = git.NewCommand(ctx, "cat-file", "-s").AddDynamicArguments(update.BlobSha).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
 		if err != nil {
 			return nil, err
 		}
 		if size, err = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64); err != nil {
-			return nil, fmt.Errorf("misformatted git cat-file output: %v", err)
+			return nil, fmt.Errorf("misformatted git cat-file output: %w", err)
 		}
 	}
 
@@ -284,7 +284,7 @@ func (b *ElasticSearchIndexer) Index(ctx context.Context, repo *repo_model.Repos
 	reqs := make([]elastic.BulkableRequest, 0)
 	if len(changes.Updates) > 0 {
 		// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
-		if err := git.EnsureValidGitRepository(git.DefaultContext, repo.RepoPath()); err != nil {
+		if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
 			log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
 			return err
 		}
@@ -348,7 +348,7 @@ func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int)
 		// FIXME: There is no way to get the position the keyword on the content currently on the same request.
 		// So we get it from content, this may made the query slower. See
 		// https://discuss.elastic.co/t/fetching-position-of-keyword-in-matched-document/94291
-		var startIndex, endIndex int = -1, -1
+		var startIndex, endIndex int
 		c, ok := hit.Highlight["content"]
 		if ok && len(c) > 0 {
 			// FIXME: Since the highlighting content will include  and   for the keywords,
diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go
index 66d76377ade70..774dcc814991d 100644
--- a/modules/indexer/code/git.go
+++ b/modules/indexer/code/git.go
@@ -29,7 +29,7 @@ type repoChanges struct {
 }
 
 func getDefaultBranchSha(ctx context.Context, repo *repo_model.Repository) (string, error) {
-	stdout, _, err := git.NewCommand(ctx, "show-ref", "-s", git.BranchPrefix+repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
+	stdout, _, err := git.NewCommand(ctx, "show-ref", "-s").AddDynamicArguments(git.BranchPrefix + repo.DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
 	if err != nil {
 		return "", err
 	}
@@ -92,7 +92,7 @@ func parseGitLsTreeOutput(stdout []byte) ([]fileUpdate, error) {
 // genesisChanges get changes to add repo to the indexer for the first time
 func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*repoChanges, error) {
 	var changes repoChanges
-	stdout, _, runErr := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", "-r", revision).RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()})
+	stdout, _, runErr := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", "-r").AddDynamicArguments(revision).RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()})
 	if runErr != nil {
 		return nil, runErr
 	}
@@ -104,7 +104,7 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
 
 // nonGenesisChanges get changes since the previous indexer update
 func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revision string) (*repoChanges, error) {
-	diffCmd := git.NewCommand(ctx, "diff", "--name-status", repo.CodeIndexerStatus.CommitSha, revision)
+	diffCmd := git.NewCommand(ctx, "diff", "--name-status").AddDynamicArguments(repo.CodeIndexerStatus.CommitSha, revision)
 	stdout, _, runErr := diffCmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
 	if runErr != nil {
 		// previous commit sha may have been removed by a force push, so
@@ -169,8 +169,8 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
 		}
 	}
 
-	cmd := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l", revision, "--")
-	cmd.AddArguments(updatedFilenames...)
+	cmd := git.NewCommand(ctx, "ls-tree", "--full-tree", "-l").AddDynamicArguments(revision).
+		AddDashesAndList(updatedFilenames...)
 	lsTreeStdout, _, err := cmd.RunStdBytes(&git.RunOpts{Dir: repo.RepoPath()})
 	if err != nil {
 		return nil, err
diff --git a/modules/indexer/issues/bleve.go b/modules/indexer/issues/bleve.go
index c298b7de3e1b0..dff1cf0c60b10 100644
--- a/modules/indexer/issues/bleve.go
+++ b/modules/indexer/issues/bleve.go
@@ -40,7 +40,7 @@ func indexerID(id int64) string {
 func idOfIndexerID(indexerID string) (int64, error) {
 	id, err := strconv.ParseInt(indexerID, 36, 64)
 	if err != nil {
-		return 0, fmt.Errorf("Unexpected indexer ID %s: %v", indexerID, err)
+		return 0, fmt.Errorf("Unexpected indexer ID %s: %w", indexerID, err)
 	}
 	return id, nil
 }
diff --git a/modules/indexer/issues/bleve_test.go b/modules/indexer/issues/bleve_test.go
index 68a7831a3f9b8..42d22f06a2db5 100644
--- a/modules/indexer/issues/bleve_test.go
+++ b/modules/indexer/issues/bleve_test.go
@@ -6,22 +6,13 @@ package issues
 
 import (
 	"context"
-	"os"
 	"testing"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
 )
 
 func TestBleveIndexAndSearch(t *testing.T) {
-	dir, err := os.MkdirTemp("", "bleve.index")
-	assert.NoError(t, err)
-	if err != nil {
-		assert.Fail(t, "Unable to create temporary directory")
-		return
-	}
-	defer util.RemoveAll(dir)
+	dir := t.TempDir()
 	indexer := NewBleveIndexer(dir)
 	defer indexer.Close()
 
@@ -30,7 +21,7 @@ func TestBleveIndexAndSearch(t *testing.T) {
 		return
 	}
 
-	err = indexer.Index([]*IndexerData{
+	err := indexer.Index([]*IndexerData{
 		{
 			ID:      1,
 			RepoID:  2,
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 6bafcbdf24d38..e8b9ff8370884 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -6,7 +6,6 @@ package issues
 
 import (
 	"context"
-	"os"
 	"path"
 	"path/filepath"
 	"testing"
@@ -14,7 +13,6 @@ import (
 
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
 
 	_ "code.gitea.io/gitea/models"
 
@@ -32,11 +30,7 @@ func TestBleveSearchIssues(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	setting.Cfg = ini.Empty()
 
-	tmpIndexerDir, err := os.MkdirTemp("", "issues-indexer")
-	if err != nil {
-		assert.Fail(t, "Unable to create temporary directory: %v", err)
-		return
-	}
+	tmpIndexerDir := t.TempDir()
 
 	setting.Cfg.Section("queue.issue_indexer").Key("DATADIR").MustString(path.Join(tmpIndexerDir, "issues.queue"))
 
@@ -44,7 +38,6 @@ func TestBleveSearchIssues(t *testing.T) {
 	setting.Indexer.IssuePath = path.Join(tmpIndexerDir, "issues.queue")
 	defer func() {
 		setting.Indexer.IssuePath = oldIssuePath
-		util.RemoveAll(tmpIndexerDir)
 	}()
 
 	setting.Indexer.IssueType = "bleve"
diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go
new file mode 100644
index 0000000000000..0bdf5a198746d
--- /dev/null
+++ b/modules/issue/template/template.go
@@ -0,0 +1,392 @@
+// 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 template
+
+import (
+	"fmt"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"code.gitea.io/gitea/modules/container"
+	api "code.gitea.io/gitea/modules/structs"
+
+	"gitea.com/go-chi/binding"
+)
+
+// Validate checks whether an IssueTemplate is considered valid, and returns the first error
+func Validate(template *api.IssueTemplate) error {
+	if err := validateMetadata(template); err != nil {
+		return err
+	}
+	if template.Type() == api.IssueTemplateTypeYaml {
+		if err := validateYaml(template); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func validateMetadata(template *api.IssueTemplate) error {
+	if strings.TrimSpace(template.Name) == "" {
+		return fmt.Errorf("'name' is required")
+	}
+	if strings.TrimSpace(template.About) == "" {
+		return fmt.Errorf("'about' is required")
+	}
+	return nil
+}
+
+func validateYaml(template *api.IssueTemplate) error {
+	if len(template.Fields) == 0 {
+		return fmt.Errorf("'body' is required")
+	}
+	ids := make(container.Set[string])
+	for idx, field := range template.Fields {
+		if err := validateID(field, idx, ids); err != nil {
+			return err
+		}
+		if err := validateLabel(field, idx); err != nil {
+			return err
+		}
+
+		position := newErrorPosition(idx, field.Type)
+		switch field.Type {
+		case api.IssueFormFieldTypeMarkdown:
+			if err := validateStringItem(position, field.Attributes, true, "value"); err != nil {
+				return err
+			}
+		case api.IssueFormFieldTypeTextarea:
+			if err := validateStringItem(position, field.Attributes, false,
+				"description",
+				"placeholder",
+				"value",
+				"render",
+			); err != nil {
+				return err
+			}
+		case api.IssueFormFieldTypeInput:
+			if err := validateStringItem(position, field.Attributes, false,
+				"description",
+				"placeholder",
+				"value",
+			); err != nil {
+				return err
+			}
+			if err := validateBoolItem(position, field.Validations, "is_number"); err != nil {
+				return err
+			}
+			if err := validateStringItem(position, field.Validations, false, "regex"); err != nil {
+				return err
+			}
+		case api.IssueFormFieldTypeDropdown:
+			if err := validateStringItem(position, field.Attributes, false, "description"); err != nil {
+				return err
+			}
+			if err := validateBoolItem(position, field.Attributes, "multiple"); err != nil {
+				return err
+			}
+			if err := validateOptions(field, idx); err != nil {
+				return err
+			}
+		case api.IssueFormFieldTypeCheckboxes:
+			if err := validateStringItem(position, field.Attributes, false, "description"); err != nil {
+				return err
+			}
+			if err := validateOptions(field, idx); err != nil {
+				return err
+			}
+		default:
+			return position.Errorf("unknown type")
+		}
+
+		if err := validateRequired(field, idx); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func validateLabel(field *api.IssueFormField, idx int) error {
+	if field.Type == api.IssueFormFieldTypeMarkdown {
+		// The label is not required for a markdown field
+		return nil
+	}
+	return validateStringItem(newErrorPosition(idx, field.Type), field.Attributes, true, "label")
+}
+
+func validateRequired(field *api.IssueFormField, idx int) error {
+	if field.Type == api.IssueFormFieldTypeMarkdown || field.Type == api.IssueFormFieldTypeCheckboxes {
+		// The label is not required for a markdown or checkboxes field
+		return nil
+	}
+	return validateBoolItem(newErrorPosition(idx, field.Type), field.Validations, "required")
+}
+
+func validateID(field *api.IssueFormField, idx int, ids container.Set[string]) error {
+	if field.Type == api.IssueFormFieldTypeMarkdown {
+		// The ID is not required for a markdown field
+		return nil
+	}
+
+	position := newErrorPosition(idx, field.Type)
+	if field.ID == "" {
+		// If the ID is empty in yaml, template.Unmarshal will auto autofill it, so it cannot be empty
+		return position.Errorf("'id' is required")
+	}
+	if binding.AlphaDashPattern.MatchString(field.ID) {
+		return position.Errorf("'id' should contain only alphanumeric, '-' and '_'")
+	}
+	if !ids.Add(field.ID) {
+		return position.Errorf("'id' should be unique")
+	}
+	return nil
+}
+
+func validateOptions(field *api.IssueFormField, idx int) error {
+	if field.Type != api.IssueFormFieldTypeDropdown && field.Type != api.IssueFormFieldTypeCheckboxes {
+		return nil
+	}
+	position := newErrorPosition(idx, field.Type)
+
+	options, ok := field.Attributes["options"].([]interface{})
+	if !ok || len(options) == 0 {
+		return position.Errorf("'options' is required and should be a array")
+	}
+
+	for optIdx, option := range options {
+		position := newErrorPosition(idx, field.Type, optIdx)
+		switch field.Type {
+		case api.IssueFormFieldTypeDropdown:
+			if _, ok := option.(string); !ok {
+				return position.Errorf("should be a string")
+			}
+		case api.IssueFormFieldTypeCheckboxes:
+			opt, ok := option.(map[string]interface{})
+			if !ok {
+				return position.Errorf("should be a dictionary")
+			}
+			if label, ok := opt["label"].(string); !ok || label == "" {
+				return position.Errorf("'label' is required and should be a string")
+			}
+
+			if required, ok := opt["required"]; ok {
+				if _, ok := required.(bool); !ok {
+					return position.Errorf("'required' should be a bool")
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func validateStringItem(position errorPosition, m map[string]interface{}, required bool, names ...string) error {
+	for _, name := range names {
+		v, ok := m[name]
+		if !ok {
+			if required {
+				return position.Errorf("'%s' is required", name)
+			}
+			return nil
+		}
+		attr, ok := v.(string)
+		if !ok {
+			return position.Errorf("'%s' should be a string", name)
+		}
+		if strings.TrimSpace(attr) == "" && required {
+			return position.Errorf("'%s' is required", name)
+		}
+	}
+	return nil
+}
+
+func validateBoolItem(position errorPosition, m map[string]interface{}, names ...string) error {
+	for _, name := range names {
+		v, ok := m[name]
+		if !ok {
+			return nil
+		}
+		if _, ok := v.(bool); !ok {
+			return position.Errorf("'%s' should be a bool", name)
+		}
+	}
+	return nil
+}
+
+type errorPosition string
+
+func (p errorPosition) Errorf(format string, a ...interface{}) error {
+	return fmt.Errorf(string(p)+": "+format, a...)
+}
+
+func newErrorPosition(fieldIdx int, fieldType api.IssueFormFieldType, optionIndex ...int) errorPosition {
+	ret := fmt.Sprintf("body[%d](%s)", fieldIdx, fieldType)
+	if len(optionIndex) > 0 {
+		ret += fmt.Sprintf(", option[%d]", optionIndex[0])
+	}
+	return errorPosition(ret)
+}
+
+// RenderToMarkdown renders template to markdown with specified values
+func RenderToMarkdown(template *api.IssueTemplate, values url.Values) string {
+	builder := &strings.Builder{}
+
+	for _, field := range template.Fields {
+		f := &valuedField{
+			IssueFormField: field,
+			Values:         values,
+		}
+		if f.ID == "" {
+			continue
+		}
+		f.WriteTo(builder)
+	}
+
+	return builder.String()
+}
+
+type valuedField struct {
+	*api.IssueFormField
+	url.Values
+}
+
+func (f *valuedField) WriteTo(builder *strings.Builder) {
+	if f.Type == api.IssueFormFieldTypeMarkdown {
+		// markdown blocks do not appear in output
+		return
+	}
+
+	// write label
+	_, _ = fmt.Fprintf(builder, "### %s\n\n", f.Label())
+
+	blankPlaceholder := "_No response_\n"
+
+	// write body
+	switch f.Type {
+	case api.IssueFormFieldTypeCheckboxes:
+		for _, option := range f.Options() {
+			checked := " "
+			if option.IsChecked() {
+				checked = "x"
+			}
+			_, _ = fmt.Fprintf(builder, "- [%s] %s\n", checked, option.Label())
+		}
+	case api.IssueFormFieldTypeDropdown:
+		var checkeds []string
+		for _, option := range f.Options() {
+			if option.IsChecked() {
+				checkeds = append(checkeds, option.Label())
+			}
+		}
+		if len(checkeds) > 0 {
+			_, _ = fmt.Fprintf(builder, "%s\n", strings.Join(checkeds, ", "))
+		} else {
+			_, _ = fmt.Fprint(builder, blankPlaceholder)
+		}
+	case api.IssueFormFieldTypeInput:
+		if value := f.Value(); value == "" {
+			_, _ = fmt.Fprint(builder, blankPlaceholder)
+		} else {
+			_, _ = fmt.Fprintf(builder, "%s\n", value)
+		}
+	case api.IssueFormFieldTypeTextarea:
+		if value := f.Value(); value == "" {
+			_, _ = fmt.Fprint(builder, blankPlaceholder)
+		} else if render := f.Render(); render != "" {
+			quotes := minQuotes(value)
+			_, _ = fmt.Fprintf(builder, "%s%s\n%s\n%s\n", quotes, f.Render(), value, quotes)
+		} else {
+			_, _ = fmt.Fprintf(builder, "%s\n", value)
+		}
+	}
+	_, _ = fmt.Fprintln(builder)
+}
+
+func (f *valuedField) Label() string {
+	if label, ok := f.Attributes["label"].(string); ok {
+		return label
+	}
+	return ""
+}
+
+func (f *valuedField) Render() string {
+	if render, ok := f.Attributes["render"].(string); ok {
+		return render
+	}
+	return ""
+}
+
+func (f *valuedField) Value() string {
+	return strings.TrimSpace(f.Get(fmt.Sprintf("form-field-" + f.ID)))
+}
+
+func (f *valuedField) Options() []*valuedOption {
+	if options, ok := f.Attributes["options"].([]interface{}); ok {
+		ret := make([]*valuedOption, 0, len(options))
+		for i, option := range options {
+			ret = append(ret, &valuedOption{
+				index: i,
+				data:  option,
+				field: f,
+			})
+		}
+		return ret
+	}
+	return nil
+}
+
+type valuedOption struct {
+	index int
+	data  interface{}
+	field *valuedField
+}
+
+func (o *valuedOption) Label() string {
+	switch o.field.Type {
+	case api.IssueFormFieldTypeDropdown:
+		if label, ok := o.data.(string); ok {
+			return label
+		}
+	case api.IssueFormFieldTypeCheckboxes:
+		if vs, ok := o.data.(map[string]interface{}); ok {
+			if v, ok := vs["label"].(string); ok {
+				return v
+			}
+		}
+	}
+	return ""
+}
+
+func (o *valuedOption) IsChecked() bool {
+	switch o.field.Type {
+	case api.IssueFormFieldTypeDropdown:
+		checks := strings.Split(o.field.Get(fmt.Sprintf("form-field-%s", o.field.ID)), ",")
+		idx := strconv.Itoa(o.index)
+		for _, v := range checks {
+			if v == idx {
+				return true
+			}
+		}
+		return false
+	case api.IssueFormFieldTypeCheckboxes:
+		return o.field.Get(fmt.Sprintf("form-field-%s-%d", o.field.ID, o.index)) == "on"
+	}
+	return false
+}
+
+var minQuotesRegex = regexp.MustCompilePOSIX("^`{3,}")
+
+// minQuotes return 3 or more back-quotes.
+// If n back-quotes exists, use n+1 back-quotes to quote.
+func minQuotes(value string) string {
+	ret := "```"
+	for _, v := range minQuotesRegex.FindAllString(value, -1) {
+		if len(v) >= len(ret) {
+			ret = v + "`"
+		}
+	}
+	return ret
+}
diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go
new file mode 100644
index 0000000000000..c3863a64a6e16
--- /dev/null
+++ b/modules/issue/template/template_test.go
@@ -0,0 +1,767 @@
+// 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 template
+
+import (
+	"net/url"
+	"testing"
+
+	"code.gitea.io/gitea/modules/json"
+	api "code.gitea.io/gitea/modules/structs"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidate(t *testing.T) {
+	tests := []struct {
+		name     string
+		filename string
+		content  string
+		want     *api.IssueTemplate
+		wantErr  string
+	}{
+		{
+			name:    "miss name",
+			content: ``,
+			wantErr: "'name' is required",
+		},
+		{
+			name: "miss about",
+			content: `
+name: "test"
+`,
+			wantErr: "'about' is required",
+		},
+		{
+			name: "miss body",
+			content: `
+name: "test"
+about: "this is about"
+`,
+			wantErr: "'body' is required",
+		},
+		{
+			name: "markdown miss value",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "markdown"
+`,
+			wantErr: "body[0](markdown): 'value' is required",
+		},
+		{
+			name: "markdown invalid value",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "markdown"
+    attributes:
+      value: true
+`,
+			wantErr: "body[0](markdown): 'value' should be a string",
+		},
+		{
+			name: "markdown empty value",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "markdown"
+    attributes:
+      value: ""
+`,
+			wantErr: "body[0](markdown): 'value' is required",
+		},
+		{
+			name: "textarea invalid id",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "textarea"
+    id: "?"
+`,
+			wantErr: "body[0](textarea): 'id' should contain only alphanumeric, '-' and '_'",
+		},
+		{
+			name: "textarea miss label",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "textarea"
+    id: "1"
+`,
+			wantErr: "body[0](textarea): 'label' is required",
+		},
+		{
+			name: "textarea conflict id",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "textarea"
+    id: "1"
+    attributes:
+      label: "a"
+  - type: "textarea"
+    id: "1"
+    attributes:
+      label: "b"
+`,
+			wantErr: "body[1](textarea): 'id' should be unique",
+		},
+		{
+			name: "textarea invalid description",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "textarea"
+    id: "1"
+    attributes:
+      label: "a"
+      description: true
+`,
+			wantErr: "body[0](textarea): 'description' should be a string",
+		},
+		{
+			name: "textarea invalid required",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "textarea"
+    id: "1"
+    attributes:
+      label: "a"
+    validations:
+      required: "on"
+`,
+			wantErr: "body[0](textarea): 'required' should be a bool",
+		},
+		{
+			name: "input invalid description",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "input"
+    id: "1"
+    attributes:
+      label: "a"
+      description: true
+`,
+			wantErr: "body[0](input): 'description' should be a string",
+		},
+		{
+			name: "input invalid is_number",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "input"
+    id: "1"
+    attributes:
+      label: "a"
+    validations:
+      is_number: "yes"
+`,
+			wantErr: "body[0](input): 'is_number' should be a bool",
+		},
+		{
+			name: "input invalid regex",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "input"
+    id: "1"
+    attributes:
+      label: "a"
+    validations:
+      regex: true
+`,
+			wantErr: "body[0](input): 'regex' should be a string",
+		},
+		{
+			name: "dropdown invalid description",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "dropdown"
+    id: "1"
+    attributes:
+      label: "a"
+      description: true
+`,
+			wantErr: "body[0](dropdown): 'description' should be a string",
+		},
+		{
+			name: "dropdown invalid multiple",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "dropdown"
+    id: "1"
+    attributes:
+      label: "a"
+      multiple: "on"
+`,
+			wantErr: "body[0](dropdown): 'multiple' should be a bool",
+		},
+		{
+			name: "checkboxes invalid description",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "checkboxes"
+    id: "1"
+    attributes:
+      label: "a"
+      description: true
+`,
+			wantErr: "body[0](checkboxes): 'description' should be a string",
+		},
+		{
+			name: "invalid type",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "video"
+    id: "1"
+    attributes:
+      label: "a"
+`,
+			wantErr: "body[0](video): unknown type",
+		},
+		{
+			name: "dropdown miss options",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "dropdown"
+    id: "1"
+    attributes:
+      label: "a"
+`,
+			wantErr: "body[0](dropdown): 'options' is required and should be a array",
+		},
+		{
+			name: "dropdown invalid options",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "dropdown"
+    id: "1"
+    attributes:
+      label: "a"
+      options:
+        - "a"
+        - true
+`,
+			wantErr: "body[0](dropdown), option[1]: should be a string",
+		},
+		{
+			name: "checkboxes invalid options",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "checkboxes"
+    id: "1"
+    attributes:
+      label: "a"
+      options:
+        - "a"
+        - true
+`,
+			wantErr: "body[0](checkboxes), option[0]: should be a dictionary",
+		},
+		{
+			name: "checkboxes option miss label",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "checkboxes"
+    id: "1"
+    attributes:
+      label: "a"
+      options:
+        - required: true
+`,
+			wantErr: "body[0](checkboxes), option[0]: 'label' is required and should be a string",
+		},
+		{
+			name: "checkboxes option invalid required",
+			content: `
+name: "test"
+about: "this is about"
+body:
+  - type: "checkboxes"
+    id: "1"
+    attributes:
+      label: "a"
+      options:
+        - label: "a"
+          required: "on"
+`,
+			wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
+		},
+		{
+			name: "valid",
+			content: `
+name: Name
+title: Title
+about: About
+labels: ["label1", "label2"]
+ref: Ref
+body:
+  - type: markdown
+    id: id1
+    attributes:
+      value: Value of the markdown
+  - type: textarea
+    id: id2
+    attributes:
+      label: Label of textarea
+      description: Description of textarea
+      placeholder: Placeholder of textarea
+      value: Value of textarea
+      render: bash
+    validations:
+      required: true
+  - type: input
+    id: id3
+    attributes:
+      label: Label of input
+      description: Description of input
+      placeholder: Placeholder of input
+      value: Value of input
+    validations:
+      required: true
+      is_number: true
+      regex: "[a-zA-Z0-9]+"
+  - type: dropdown
+    id: id4
+    attributes:
+      label: Label of dropdown
+      description: Description of dropdown
+      multiple: true
+      options:
+        - Option 1 of dropdown
+        - Option 2 of dropdown
+        - Option 3 of dropdown
+    validations:
+      required: true
+  - type: checkboxes
+    id: id5
+    attributes:
+      label: Label of checkboxes
+      description: Description of checkboxes
+      options:
+        - label: Option 1 of checkboxes
+          required: true
+        - label: Option 2 of checkboxes
+          required: false
+        - label: Option 3 of checkboxes
+          required: true
+`,
+			want: &api.IssueTemplate{
+				Name:   "Name",
+				Title:  "Title",
+				About:  "About",
+				Labels: []string{"label1", "label2"},
+				Ref:    "Ref",
+				Fields: []*api.IssueFormField{
+					{
+						Type: "markdown",
+						ID:   "id1",
+						Attributes: map[string]interface{}{
+							"value": "Value of the markdown",
+						},
+					},
+					{
+						Type: "textarea",
+						ID:   "id2",
+						Attributes: map[string]interface{}{
+							"label":       "Label of textarea",
+							"description": "Description of textarea",
+							"placeholder": "Placeholder of textarea",
+							"value":       "Value of textarea",
+							"render":      "bash",
+						},
+						Validations: map[string]interface{}{
+							"required": true,
+						},
+					},
+					{
+						Type: "input",
+						ID:   "id3",
+						Attributes: map[string]interface{}{
+							"label":       "Label of input",
+							"description": "Description of input",
+							"placeholder": "Placeholder of input",
+							"value":       "Value of input",
+						},
+						Validations: map[string]interface{}{
+							"required":  true,
+							"is_number": true,
+							"regex":     "[a-zA-Z0-9]+",
+						},
+					},
+					{
+						Type: "dropdown",
+						ID:   "id4",
+						Attributes: map[string]interface{}{
+							"label":       "Label of dropdown",
+							"description": "Description of dropdown",
+							"multiple":    true,
+							"options": []interface{}{
+								"Option 1 of dropdown",
+								"Option 2 of dropdown",
+								"Option 3 of dropdown",
+							},
+						},
+						Validations: map[string]interface{}{
+							"required": true,
+						},
+					},
+					{
+						Type: "checkboxes",
+						ID:   "id5",
+						Attributes: map[string]interface{}{
+							"label":       "Label of checkboxes",
+							"description": "Description of checkboxes",
+							"options": []interface{}{
+								map[string]interface{}{"label": "Option 1 of checkboxes", "required": true},
+								map[string]interface{}{"label": "Option 2 of checkboxes", "required": false},
+								map[string]interface{}{"label": "Option 3 of checkboxes", "required": true},
+							},
+						},
+					},
+				},
+				FileName: "test.yaml",
+			},
+			wantErr: "",
+		},
+		{
+			name: "single label",
+			content: `
+name: Name
+title: Title
+about: About
+labels: label1
+ref: Ref
+body:
+  - type: markdown
+    id: id1
+    attributes:
+      value: Value of the markdown
+`,
+			want: &api.IssueTemplate{
+				Name:   "Name",
+				Title:  "Title",
+				About:  "About",
+				Labels: []string{"label1"},
+				Ref:    "Ref",
+				Fields: []*api.IssueFormField{
+					{
+						Type: "markdown",
+						ID:   "id1",
+						Attributes: map[string]interface{}{
+							"value": "Value of the markdown",
+						},
+					},
+				},
+				FileName: "test.yaml",
+			},
+			wantErr: "",
+		},
+		{
+			name: "comma-delimited labels",
+			content: `
+name: Name
+title: Title
+about: About
+labels: label1,label2,,label3 ,,
+ref: Ref
+body:
+  - type: markdown
+    id: id1
+    attributes:
+      value: Value of the markdown
+`,
+			want: &api.IssueTemplate{
+				Name:   "Name",
+				Title:  "Title",
+				About:  "About",
+				Labels: []string{"label1", "label2", "label3"},
+				Ref:    "Ref",
+				Fields: []*api.IssueFormField{
+					{
+						Type: "markdown",
+						ID:   "id1",
+						Attributes: map[string]interface{}{
+							"value": "Value of the markdown",
+						},
+					},
+				},
+				FileName: "test.yaml",
+			},
+			wantErr: "",
+		},
+		{
+			name: "empty string as labels",
+			content: `
+name: Name
+title: Title
+about: About
+labels: ''
+ref: Ref
+body:
+  - type: markdown
+    id: id1
+    attributes:
+      value: Value of the markdown
+`,
+			want: &api.IssueTemplate{
+				Name:   "Name",
+				Title:  "Title",
+				About:  "About",
+				Labels: nil,
+				Ref:    "Ref",
+				Fields: []*api.IssueFormField{
+					{
+						Type: "markdown",
+						ID:   "id1",
+						Attributes: map[string]interface{}{
+							"value": "Value of the markdown",
+						},
+					},
+				},
+				FileName: "test.yaml",
+			},
+			wantErr: "",
+		},
+		{
+			name:     "comma delimited labels in markdown",
+			filename: "test.md",
+			content: `---
+name: Name
+title: Title
+about: About
+labels: label1,label2,,label3 ,,
+ref: Ref
+---
+Content
+`,
+			want: &api.IssueTemplate{
+				Name:     "Name",
+				Title:    "Title",
+				About:    "About",
+				Labels:   []string{"label1", "label2", "label3"},
+				Ref:      "Ref",
+				Fields:   nil,
+				Content:  "Content\n",
+				FileName: "test.md",
+			},
+			wantErr: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			filename := "test.yaml"
+			if tt.filename != "" {
+				filename = tt.filename
+			}
+			tmpl, err := unmarshal(filename, []byte(tt.content))
+			require.NoError(t, err)
+			if tt.wantErr != "" {
+				require.EqualError(t, Validate(tmpl), tt.wantErr)
+			} else {
+				require.NoError(t, Validate(tmpl))
+				want, _ := json.Marshal(tt.want)
+				got, _ := json.Marshal(tmpl)
+				require.JSONEq(t, string(want), string(got))
+			}
+		})
+	}
+}
+
+func TestRenderToMarkdown(t *testing.T) {
+	type args struct {
+		template string
+		values   url.Values
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "normal",
+			args: args{
+				template: `
+name: Name
+title: Title
+about: About
+labels: ["label1", "label2"]
+ref: Ref
+body:
+  - type: markdown
+    id: id1
+    attributes:
+      value: Value of the markdown
+  - type: textarea
+    id: id2
+    attributes:
+      label: Label of textarea
+      description: Description of textarea
+      placeholder: Placeholder of textarea
+      value: Value of textarea
+      render: bash
+    validations:
+      required: true
+  - type: input
+    id: id3
+    attributes:
+      label: Label of input
+      description: Description of input
+      placeholder: Placeholder of input
+      value: Value of input
+    validations:
+      required: true
+      is_number: true
+      regex: "[a-zA-Z0-9]+"
+  - type: dropdown
+    id: id4
+    attributes:
+      label: Label of dropdown
+      description: Description of dropdown
+      multiple: true
+      options:
+        - Option 1 of dropdown
+        - Option 2 of dropdown
+        - Option 3 of dropdown
+    validations:
+      required: true
+  - type: checkboxes
+    id: id5
+    attributes:
+      label: Label of checkboxes
+      description: Description of checkboxes
+      options:
+        - label: Option 1 of checkboxes
+          required: true
+        - label: Option 2 of checkboxes
+          required: false
+        - label: Option 3 of checkboxes
+          required: true
+`,
+				values: map[string][]string{
+					"form-field-id2":   {"Value of id2"},
+					"form-field-id3":   {"Value of id3"},
+					"form-field-id4":   {"0,1"},
+					"form-field-id5-0": {"on"},
+					"form-field-id5-2": {"on"},
+				},
+			},
+			want: `### Label of textarea
+
+` + "```bash\nValue of id2\n```" + `
+
+### Label of input
+
+Value of id3
+
+### Label of dropdown
+
+Option 1 of dropdown, Option 2 of dropdown
+
+### Label of checkboxes
+
+- [x] Option 1 of checkboxes
+- [ ] Option 2 of checkboxes
+- [x] Option 3 of checkboxes
+
+`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			template, err := Unmarshal("test.yaml", []byte(tt.args.template))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if got := RenderToMarkdown(template, tt.args.values); got != tt.want {
+				t.Errorf("RenderToMarkdown() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_minQuotes(t *testing.T) {
+	type args struct {
+		value string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "without quote",
+			args: args{
+				value: "Hello\nWorld",
+			},
+			want: "```",
+		},
+		{
+			name: "with 1 quote",
+			args: args{
+				value: "Hello\nWorld\n`text`\n",
+			},
+			want: "```",
+		},
+		{
+			name: "with 3 quotes",
+			args: args{
+				value: "Hello\nWorld\n`text`\n```go\ntext\n```\n",
+			},
+			want: "````",
+		},
+		{
+			name: "with more quotes",
+			args: args{
+				value: "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n",
+			},
+			want: "```````````",
+		},
+		{
+			name: "not leading quotes",
+			args: args{
+				value: "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n",
+			},
+			want: "```",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := minQuotes(tt.args.value); got != tt.want {
+				t.Errorf("minQuotes() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go
new file mode 100644
index 0000000000000..9b684f1bf7884
--- /dev/null
+++ b/modules/issue/template/unmarshal.go
@@ -0,0 +1,139 @@
+// 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 template
+
+import (
+	"fmt"
+	"io"
+	"path"
+	"strconv"
+
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/markup/markdown"
+	"code.gitea.io/gitea/modules/setting"
+	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/util"
+
+	"gopkg.in/yaml.v3"
+)
+
+// CouldBe indicates a file with the filename could be a template,
+// it is a low cost check before further processing.
+func CouldBe(filename string) bool {
+	it := &api.IssueTemplate{
+		FileName: filename,
+	}
+	return it.Type() != ""
+}
+
+// Unmarshal parses out a valid template from the content
+func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
+	it, err := unmarshal(filename, content)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := Validate(it); err != nil {
+		return nil, err
+	}
+
+	return it, nil
+}
+
+// UnmarshalFromEntry parses out a valid template from the blob in entry
+func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
+	return unmarshalFromEntry(entry, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here
+}
+
+// UnmarshalFromCommit parses out a valid template from the commit
+func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) {
+	entry, err := commit.GetTreeEntryByPath(filename)
+	if err != nil {
+		return nil, fmt.Errorf("get entry for %q: %w", filename, err)
+	}
+	return unmarshalFromEntry(entry, filename)
+}
+
+// UnmarshalFromRepo parses out a valid template from the head commit of the branch
+func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) {
+	commit, err := repo.GetBranchCommit(branch)
+	if err != nil {
+		return nil, fmt.Errorf("get commit on branch %q: %w", branch, err)
+	}
+
+	return UnmarshalFromCommit(commit, filename)
+}
+
+func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) {
+	if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize {
+		return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size)
+	}
+
+	r, err := entry.Blob().DataAsync()
+	if err != nil {
+		return nil, fmt.Errorf("data async: %w", err)
+	}
+	defer r.Close()
+
+	content, err := io.ReadAll(r)
+	if err != nil {
+		return nil, fmt.Errorf("read all: %w", err)
+	}
+
+	return Unmarshal(filename, content)
+}
+
+func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
+	it := &api.IssueTemplate{
+		FileName: filename,
+	}
+
+	// Compatible with treating description as about
+	compatibleTemplate := &struct {
+		About string `yaml:"description"`
+	}{}
+
+	if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
+		if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil {
+			// The only thing we know here is that we can't extract metadata from the content,
+			// it's hard to tell if metadata doesn't exist or metadata isn't valid.
+			// There's an example template:
+			//
+			//    ---
+			//    # Title
+			//    ---
+			//    Content
+			//
+			// It could be a valid markdown with two horizontal lines, or an invalid markdown with wrong metadata.
+
+			it.Content = string(content)
+			it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
+			it.About, _ = util.SplitStringAtByteN(it.Content, 80)
+		} else {
+			it.Content = templateBody
+			if it.About == "" {
+				if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
+					it.About = compatibleTemplate.About
+				}
+			}
+		}
+	} else if typ == api.IssueTemplateTypeYaml {
+		if err := yaml.Unmarshal(content, it); err != nil {
+			return nil, fmt.Errorf("yaml unmarshal: %w", err)
+		}
+		if it.About == "" {
+			if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" {
+				it.About = compatibleTemplate.About
+			}
+		}
+		for i, v := range it.Fields {
+			if v.ID == "" {
+				v.ID = strconv.Itoa(i)
+			}
+		}
+	}
+
+	return it, nil
+}
diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go
index c794a1feccc6f..0eedf4de1798b 100644
--- a/modules/lfs/content_store.go
+++ b/modules/lfs/content_store.go
@@ -8,7 +8,6 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"errors"
-	"fmt"
 	"hash"
 	"io"
 	"os"
@@ -24,21 +23,6 @@ var (
 	ErrSizeMismatch = errors.New("Content size does not match")
 )
 
-// ErrRangeNotSatisfiable represents an error which request range is not satisfiable.
-type ErrRangeNotSatisfiable struct {
-	FromByte int64
-}
-
-// IsErrRangeNotSatisfiable returns true if the error is an ErrRangeNotSatisfiable
-func IsErrRangeNotSatisfiable(err error) bool {
-	_, ok := err.(ErrRangeNotSatisfiable)
-	return ok
-}
-
-func (err ErrRangeNotSatisfiable) Error() string {
-	return fmt.Sprintf("Requested range %d is not satisfiable", err.FromByte)
-}
-
 // ContentStore provides a simple file system based storage.
 type ContentStore struct {
 	storage.ObjectStorage
diff --git a/modules/log/conn.go b/modules/log/conn.go
index bd1d76d071d91..155f2866cae3b 100644
--- a/modules/log/conn.go
+++ b/modules/log/conn.go
@@ -108,7 +108,7 @@ func NewConn() LoggerProvider {
 func (log *ConnLogger) Init(jsonconfig string) error {
 	err := json.Unmarshal([]byte(jsonconfig), log)
 	if err != nil {
-		return fmt.Errorf("Unable to parse JSON: %v", err)
+		return fmt.Errorf("Unable to parse JSON: %w", err)
 	}
 	log.NewWriterLogger(&connWriter{
 		ReconnectOnMsg: log.ReconnectOnMsg,
diff --git a/modules/log/console.go b/modules/log/console.go
index 8c78add4743ed..fc7c9b4967255 100644
--- a/modules/log/console.go
+++ b/modules/log/console.go
@@ -54,7 +54,7 @@ func NewConsoleLogger() LoggerProvider {
 func (log *ConsoleLogger) Init(config string) error {
 	err := json.Unmarshal([]byte(config), log)
 	if err != nil {
-		return fmt.Errorf("Unable to parse JSON: %v", err)
+		return fmt.Errorf("Unable to parse JSON: %w", err)
 	}
 	if log.Stderr {
 		log.NewWriterLogger(&nopWriteCloser{
diff --git a/modules/log/event.go b/modules/log/event.go
index 41bb241da840a..aebd1562125ad 100644
--- a/modules/log/event.go
+++ b/modules/log/event.go
@@ -293,9 +293,9 @@ func (m *MultiChannelledLog) ReleaseReopen() error {
 	for _, logger := range m.loggers {
 		if err := logger.ReleaseReopen(); err != nil {
 			if accumulatedErr == nil {
-				accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v", logger.GetName(), err)
+				accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %w", logger.GetName(), err)
 			} else {
-				accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v & %v", logger.GetName(), err, accumulatedErr)
+				accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v & %w", logger.GetName(), err, accumulatedErr)
 			}
 		}
 	}
diff --git a/modules/log/file.go b/modules/log/file.go
index d9a529e67fb0b..d0cba7817c361 100644
--- a/modules/log/file.go
+++ b/modules/log/file.go
@@ -93,6 +93,7 @@ func NewFileLogger() LoggerProvider {
 
 // Init file logger with json config.
 // config like:
+//
 //	{
 //	"filename":"log/gogs.log",
 //	"maxsize":1<<30,
@@ -102,7 +103,7 @@ func NewFileLogger() LoggerProvider {
 //	}
 func (log *FileLogger) Init(config string) error {
 	if err := json.Unmarshal([]byte(config), log); err != nil {
-		return fmt.Errorf("Unable to parse JSON: %v", err)
+		return fmt.Errorf("Unable to parse JSON: %w", err)
 	}
 	if len(log.Filename) == 0 {
 		return errors.New("config must have filename")
@@ -144,7 +145,7 @@ func (log *FileLogger) initFd() error {
 	fd := log.mw.fd
 	finfo, err := fd.Stat()
 	if err != nil {
-		return fmt.Errorf("get stat: %v", err)
+		return fmt.Errorf("get stat: %w", err)
 	}
 	log.maxsizeCursize = int(finfo.Size())
 	log.dailyOpenDate = time.Now().Day()
@@ -177,7 +178,7 @@ func (log *FileLogger) DoRotate() error {
 		// close fd before rename
 		// Rename the file to its newfound home
 		if err = util.Rename(log.Filename, fname); err != nil {
-			return fmt.Errorf("Rotate: %v", err)
+			return fmt.Errorf("Rotate: %w", err)
 		}
 
 		if log.Compress {
@@ -186,7 +187,7 @@ func (log *FileLogger) DoRotate() error {
 
 		// re-start logger
 		if err = log.StartLogger(); err != nil {
-			return fmt.Errorf("Rotate StartLogger: %v", err)
+			return fmt.Errorf("Rotate StartLogger: %w", err)
 		}
 
 		go log.deleteOldLog()
@@ -235,7 +236,7 @@ func (log *FileLogger) deleteOldLog() {
 		if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*log.Maxdays) {
 			if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) {
 				if err := util.Remove(path); err != nil {
-					returnErr = fmt.Errorf("Failed to remove %s: %v", path, err)
+					returnErr = fmt.Errorf("Failed to remove %s: %w", path, err)
 				}
 			}
 		}
diff --git a/modules/log/file_test.go b/modules/log/file_test.go
index c3074b69df9e3..cc2b9fe077212 100644
--- a/modules/log/file_test.go
+++ b/modules/log/file_test.go
@@ -14,15 +14,11 @@ import (
 	"testing"
 	"time"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"github.com/stretchr/testify/assert"
 )
 
 func TestFileLoggerFails(t *testing.T) {
-	tmpDir, err := os.MkdirTemp("", "TestFileLogger")
-	assert.NoError(t, err)
-	defer util.RemoveAll(tmpDir)
+	tmpDir := t.TempDir()
 
 	prefix := "TestPrefix "
 	level := INFO
@@ -34,7 +30,7 @@ func TestFileLoggerFails(t *testing.T) {
 	// assert.True(t, ok)
 
 	// Fail if there is bad json
-	err = fileLogger.Init("{")
+	err := fileLogger.Init("{")
 	assert.Error(t, err)
 
 	// Fail if there is no filename
@@ -47,9 +43,7 @@ func TestFileLoggerFails(t *testing.T) {
 }
 
 func TestFileLogger(t *testing.T) {
-	tmpDir, err := os.MkdirTemp("", "TestFileLogger")
-	assert.NoError(t, err)
-	defer util.RemoveAll(tmpDir)
+	tmpDir := t.TempDir()
 
 	prefix := "TestPrefix "
 	level := INFO
@@ -150,9 +144,7 @@ func TestFileLogger(t *testing.T) {
 }
 
 func TestCompressFileLogger(t *testing.T) {
-	tmpDir, err := os.MkdirTemp("", "TestFileLogger")
-	assert.NoError(t, err)
-	defer util.RemoveAll(tmpDir)
+	tmpDir := t.TempDir()
 
 	prefix := "TestPrefix "
 	level := INFO
@@ -210,9 +202,7 @@ func TestCompressFileLogger(t *testing.T) {
 }
 
 func TestCompressOldFile(t *testing.T) {
-	tmpDir, err := os.MkdirTemp("", "TestFileLogger")
-	assert.NoError(t, err)
-	defer util.RemoveAll(tmpDir)
+	tmpDir := t.TempDir()
 	fname := filepath.Join(tmpDir, "test")
 	nonGzip := filepath.Join(tmpDir, "test-nonGzip")
 
diff --git a/modules/log/log.go b/modules/log/log.go
index f805a36231bd8..4303ecf4c03c4 100644
--- a/modules/log/log.go
+++ b/modules/log/log.go
@@ -219,9 +219,9 @@ func ReleaseReopen() error {
 		logger := value.(*MultiChannelledLogger)
 		if err := logger.ReleaseReopen(); err != nil {
 			if accumulatedErr == nil {
-				accumulatedErr = fmt.Errorf("Error reopening %s: %v", key.(string), err)
+				accumulatedErr = fmt.Errorf("Error reopening %s: %w", key.(string), err)
 			} else {
-				accumulatedErr = fmt.Errorf("Error reopening %s: %v & %v", key.(string), err, accumulatedErr)
+				accumulatedErr = fmt.Errorf("Error reopening %s: %v & %w", key.(string), err, accumulatedErr)
 			}
 		}
 		return true
diff --git a/modules/log/multichannel.go b/modules/log/multichannel.go
index 273df81df15e3..519abf663d99d 100644
--- a/modules/log/multichannel.go
+++ b/modules/log/multichannel.go
@@ -33,7 +33,7 @@ func newLogger(name string, buffer int64) *MultiChannelledLogger {
 func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error {
 	eventLogger, err := NewChannelledLog(l.ctx, name, provider, config, l.bufferLength)
 	if err != nil {
-		return fmt.Errorf("Failed to create sublogger (%s): %v", name, err)
+		return fmt.Errorf("failed to create sublogger (%s): %w", name, err)
 	}
 
 	l.MultiChannelledLog.DelLogger(name)
@@ -41,9 +41,9 @@ func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error {
 	err = l.MultiChannelledLog.AddLogger(eventLogger)
 	if err != nil {
 		if IsErrDuplicateName(err) {
-			return fmt.Errorf("Duplicate named sublogger %s %v", name, l.MultiChannelledLog.GetEventLoggerNames())
+			return fmt.Errorf("%w other names: %v", err, l.MultiChannelledLog.GetEventLoggerNames())
 		}
-		return fmt.Errorf("Failed to add sublogger (%s): %v", name, err)
+		return fmt.Errorf("failed to add sublogger (%s): %w", name, err)
 	}
 
 	return nil
diff --git a/modules/log/smtp.go b/modules/log/smtp.go
index c5163292e66c3..61af50b81a9dc 100644
--- a/modules/log/smtp.go
+++ b/modules/log/smtp.go
@@ -48,6 +48,7 @@ func NewSMTPLogger() LoggerProvider {
 
 // Init smtp writer with json config.
 // config like:
+//
 //	{
 //		"Username":"example@gmail.com",
 //		"password:"password",
@@ -59,7 +60,7 @@ func NewSMTPLogger() LoggerProvider {
 func (log *SMTPLogger) Init(jsonconfig string) error {
 	err := json.Unmarshal([]byte(jsonconfig), log)
 	if err != nil {
-		return fmt.Errorf("Unable to parse JSON: %v", err)
+		return fmt.Errorf("Unable to parse JSON: %w", err)
 	}
 	log.NewWriterLogger(&smtpWriter{
 		owner: log,
diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go
index 821b3e6387a1a..d07f5e6090137 100644
--- a/modules/markup/common/footnote.go
+++ b/modules/markup/common/footnote.go
@@ -203,9 +203,8 @@ func (b *footnoteBlockParser) Open(parent ast.Node, reader text.Reader, pc parse
 		return nil, parser.NoChildren
 	}
 	open := pos + 1
-	closes := 0
 	closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint
-	closes = pos + 1 + closure
+	closes := pos + 1 + closure
 	next := closes + 1
 	if closure > -1 {
 		if next >= len(line) || line[next] != ':' {
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go
index 23dd45ba0a1f2..0eeb2d70a5f14 100644
--- a/modules/markup/external/external.go
+++ b/modules/markup/external/external.go
@@ -90,7 +90,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
 		// write to temp file
 		f, err := os.CreateTemp("", "gitea_input")
 		if err != nil {
-			return fmt.Errorf("%s create temp file when rendering %s failed: %v", p.Name(), p.Command, err)
+			return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err)
 		}
 		tmpPath := f.Name()
 		defer func() {
@@ -102,12 +102,12 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
 		_, err = io.Copy(f, input)
 		if err != nil {
 			f.Close()
-			return fmt.Errorf("%s write data to temp file when rendering %s failed: %v", p.Name(), p.Command, err)
+			return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err)
 		}
 
 		err = f.Close()
 		if err != nil {
-			return fmt.Errorf("%s close temp file when rendering %s failed: %v", p.Name(), p.Command, err)
+			return fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err)
 		}
 		args = append(args, f.Name())
 	}
@@ -137,7 +137,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
 	process.SetSysProcAttribute(cmd)
 
 	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("%s render run command %s %v failed: %v", p.Name(), commands[0], args, err)
+		return fmt.Errorf("%s render run command %s %v failed: %w", p.Name(), commands[0], args, err)
 	}
 	return nil
 }
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 69d9ba3ef2a46..ae00c3905fe8b 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -603,8 +603,14 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 			start = loc.End
 			continue
 		}
-		replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mention[1:]), mention, "mention"))
-		node = node.NextSibling.NextSibling
+		mentionedUsername := mention[1:]
+
+		if processorHelper.IsUsernameMentionable != nil && processorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
+			replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention"))
+			node = node.NextSibling.NextSibling
+		} else {
+			node = node.NextSibling
+		}
 		start = 0
 	}
 }
@@ -841,9 +847,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
 
 		// Repos with external issue trackers might still need to reference local PRs
 		// We need to concern with the first one that shows up in the text, whichever it is
-		if hasExtTrackFormat && !isNumericStyle {
+		if hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
 			// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
-			if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start {
+			// Allow a free-pass when non-numeric pattern wasn't found.
+			if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) {
 				found = foundNumeric
 				ref = refNumeric
 			}
@@ -1175,7 +1182,7 @@ func genDefaultLinkProcessor(defaultLink string) processor {
 		node.DataAtom = atom.A
 		node.Attr = []html.Attribute{
 			{Key: "href", Val: defaultLink},
-			{Key: "class", Val: "default-link"},
+			{Key: "class", Val: "default-link muted"},
 		}
 		node.FirstChild, node.LastChild = ch, ch
 	}
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index a7cf81250bf0e..e57187a67782b 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -7,6 +7,7 @@ package markup_test
 import (
 	"context"
 	"io"
+	"os"
 	"strings"
 	"testing"
 
@@ -24,7 +25,7 @@ import (
 var localMetas = map[string]string{
 	"user":     "gogits",
 	"repo":     "gogs",
-	"repoPath": "../../integrations/gitea-repositories-meta/user13/repo11.git/",
+	"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
 }
 
 func TestMain(m *testing.M) {
@@ -32,6 +33,7 @@ func TestMain(m *testing.M) {
 	if err := git.InitSimple(context.Background()); err != nil {
 		log.Fatal("git init failed, err: %v", err)
 	}
+	os.Exit(m.Run())
 }
 
 func TestRender_Commits(t *testing.T) {
@@ -336,7 +338,7 @@ func TestRender_emoji(t *testing.T) {
 		`Some text with 😄 😄  2 emoji next to each other
`)
 	test(
 		"😎🤪🔐🤑❓",
-		`😎 🤪 🔐 🤑 ❓ 
`)
+		`😎 🤪 🔐 🤑 ❓ 
`)
 
 	// should match nothing
 	test(
diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go
index 5191d94cdd85a..c82d5e5e73396 100644
--- a/modules/markup/markdown/ast.go
+++ b/modules/markup/markdown/ast.go
@@ -144,3 +144,39 @@ func IsIcon(node ast.Node) bool {
 	_, ok := node.(*Icon)
 	return ok
 }
+
+// ColorPreview is an inline for a color preview
+type ColorPreview struct {
+	ast.BaseInline
+	Color []byte
+}
+
+// Dump implements Node.Dump.
+func (n *ColorPreview) Dump(source []byte, level int) {
+	m := map[string]string{}
+	m["Color"] = string(n.Color)
+	ast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindColorPreview is the NodeKind for ColorPreview
+var KindColorPreview = ast.NewNodeKind("ColorPreview")
+
+// Kind implements Node.Kind.
+func (n *ColorPreview) Kind() ast.NodeKind {
+	return KindColorPreview
+}
+
+// NewColorPreview returns a new Span node.
+func NewColorPreview(color []byte) *ColorPreview {
+	return &ColorPreview{
+		BaseInline: ast.BaseInline{},
+		Color:      color,
+	}
+}
+
+// IsColorPreview returns true if the given node implements the ColorPreview interface,
+// otherwise false.
+func IsColorPreview(node ast.Node) bool {
+	_, ok := node.(*ColorPreview)
+	return ok
+}
diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go
new file mode 100644
index 0000000000000..3f5ebec90899e
--- /dev/null
+++ b/modules/markup/markdown/convertyaml.go
@@ -0,0 +1,84 @@
+// 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 markdown
+
+import (
+	"github.com/yuin/goldmark/ast"
+	east "github.com/yuin/goldmark/extension/ast"
+	"gopkg.in/yaml.v3"
+)
+
+func nodeToTable(meta *yaml.Node) ast.Node {
+	for {
+		if meta == nil {
+			return nil
+		}
+		switch meta.Kind {
+		case yaml.DocumentNode:
+			meta = meta.Content[0]
+			continue
+		default:
+		}
+		break
+	}
+	switch meta.Kind {
+	case yaml.MappingNode:
+		return mappingNodeToTable(meta)
+	case yaml.SequenceNode:
+		return sequenceNodeToTable(meta)
+	default:
+		return ast.NewString([]byte(meta.Value))
+	}
+}
+
+func mappingNodeToTable(meta *yaml.Node) ast.Node {
+	table := east.NewTable()
+	alignments := []east.Alignment{}
+	for i := 0; i < len(meta.Content); i += 2 {
+		alignments = append(alignments, east.AlignNone)
+	}
+
+	headerRow := east.NewTableRow(alignments)
+	valueRow := east.NewTableRow(alignments)
+	for i := 0; i < len(meta.Content); i += 2 {
+		cell := east.NewTableCell()
+
+		cell.AppendChild(cell, nodeToTable(meta.Content[i]))
+		headerRow.AppendChild(headerRow, cell)
+
+		if i+1 < len(meta.Content) {
+			cell = east.NewTableCell()
+			cell.AppendChild(cell, nodeToTable(meta.Content[i+1]))
+			valueRow.AppendChild(valueRow, cell)
+		}
+	}
+
+	table.AppendChild(table, east.NewTableHeader(headerRow))
+	table.AppendChild(table, valueRow)
+	return table
+}
+
+func sequenceNodeToTable(meta *yaml.Node) ast.Node {
+	table := east.NewTable()
+	alignments := []east.Alignment{east.AlignNone}
+	for _, item := range meta.Content {
+		row := east.NewTableRow(alignments)
+		cell := east.NewTableCell()
+		cell.AppendChild(cell, nodeToTable(item))
+		row.AppendChild(row, cell)
+		table.AppendChild(table, row)
+	}
+	return table
+}
+
+func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
+	details := NewDetails()
+	summary := NewSummary()
+	summary.AppendChild(summary, NewIcon(icon))
+	details.AppendChild(details, summary)
+	details.AppendChild(details, nodeToTable(meta))
+
+	return details
+}
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 1750128dec858..1a36681366193 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -10,12 +10,13 @@ import (
 	"regexp"
 	"strings"
 
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/common"
 	"code.gitea.io/gitea/modules/setting"
 	giteautil "code.gitea.io/gitea/modules/util"
 
-	meta "github.com/yuin/goldmark-meta"
+	"github.com/microcosm-cc/bluemonday/css"
 	"github.com/yuin/goldmark/ast"
 	east "github.com/yuin/goldmark/extension/ast"
 	"github.com/yuin/goldmark/parser"
@@ -32,20 +33,12 @@ type ASTTransformer struct{}
 
 // Transform transforms the given AST tree.
 func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
-	metaData := meta.GetItems(pc)
 	firstChild := node.FirstChild()
 	createTOC := false
 	ctx := pc.Get(renderContextKey).(*markup.RenderContext)
-	rc := &RenderConfig{
-		Meta: "table",
-		Icon: "table",
-		Lang: "",
-	}
-
-	if metaData != nil {
-		rc.ToRenderConfig(metaData)
-
-		metaNode := rc.toMetaNode(metaData)
+	rc := pc.Get(renderConfigKey).(*RenderConfig)
+	if rc.yamlNode != nil {
+		metaNode := rc.toMetaNode()
 		if metaNode != nil {
 			node.InsertBefore(node, firstChild, metaNode)
 		}
@@ -186,6 +179,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 					v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
 				}
 			}
+		case *ast.CodeSpan:
+			colorContent := n.Text(reader.Source())
+			if css.ColorHandler(strings.ToLower(string(colorContent))) {
+				v.AppendChild(v, NewColorPreview(colorContent))
+			}
 		}
 		return ast.WalkContinue, nil
 	})
@@ -207,7 +205,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 }
 
 type prefixedIDs struct {
-	values map[string]bool
+	values container.Set[string]
 }
 
 // Generate generates a new element id.
@@ -228,14 +226,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
 	if !bytes.HasPrefix(result, []byte("user-content-")) {
 		result = append([]byte("user-content-"), result...)
 	}
-	if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
-		p.values[util.BytesToReadOnlyString(result)] = true
+	if p.values.Add(util.BytesToReadOnlyString(result)) {
 		return result
 	}
 	for i := 1; ; i++ {
 		newResult := fmt.Sprintf("%s-%d", result, i)
-		if _, ok := p.values[newResult]; !ok {
-			p.values[newResult] = true
+		if p.values.Add(newResult) {
 			return []byte(newResult)
 		}
 	}
@@ -243,12 +239,12 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
 
 // Put puts a given element id to the used ids table.
 func (p *prefixedIDs) Put(value []byte) {
-	p.values[util.BytesToReadOnlyString(value)] = true
+	p.values.Add(util.BytesToReadOnlyString(value))
 }
 
 func newPrefixedIDs() *prefixedIDs {
 	return &prefixedIDs{
-		values: map[string]bool{},
+		values: make(container.Set[string]),
 	}
 }
 
@@ -276,10 +272,43 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 	reg.Register(KindDetails, r.renderDetails)
 	reg.Register(KindSummary, r.renderSummary)
 	reg.Register(KindIcon, r.renderIcon)
+	reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
 	reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
 	reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
 }
 
+// renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements.
+// See #21474 for reference
+func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
+	if entering {
+		if n.Attributes() != nil {
+			_, _ = w.WriteString("')
+		} else {
+			_, _ = w.WriteString("")
+		}
+		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
+			switch v := c.(type) {
+			case *ast.Text:
+				segment := v.Segment
+				value := segment.Value(source)
+				if bytes.HasSuffix(value, []byte("\n")) {
+					r.Writer.RawWrite(w, value[:len(value)-1])
+					r.Writer.RawWrite(w, []byte(" "))
+				} else {
+					r.Writer.RawWrite(w, value)
+				}
+			case *ColorPreview:
+				_, _ = w.WriteString(fmt.Sprintf(`")
+	return ast.WalkContinue, nil
+}
+
 func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 	n := node.(*ast.Document)
 
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 37e11e606fce5..fa289986ccec3 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -14,12 +14,13 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/common"
+	"code.gitea.io/gitea/modules/markup/markdown/math"
 	"code.gitea.io/gitea/modules/setting"
 	giteautil "code.gitea.io/gitea/modules/util"
 
-	chromahtml "github.com/alecthomas/chroma/formatters/html"
+	chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
 	"github.com/yuin/goldmark"
-	highlighting "github.com/yuin/goldmark-highlighting"
+	highlighting "github.com/yuin/goldmark-highlighting/v2"
 	meta "github.com/yuin/goldmark-meta"
 	"github.com/yuin/goldmark/extension"
 	"github.com/yuin/goldmark/parser"
@@ -38,6 +39,7 @@ var (
 	isWikiKey        = parser.NewContextKey()
 	renderMetasKey   = parser.NewContextKey()
 	renderContextKey = parser.NewContextKey()
+	renderConfigKey  = parser.NewContextKey()
 )
 
 type limitWriter struct {
@@ -98,7 +100,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
 							languageStr := string(language)
 
 							preClasses := []string{"code-block"}
-							if languageStr == "mermaid" {
+							if languageStr == "mermaid" || languageStr == "math" {
 								preClasses = append(preClasses, "is-loading")
 							}
 
@@ -120,6 +122,9 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
 						}
 					}),
 				),
+				math.NewExtension(
+					math.Enabled(setting.Markdown.EnableMath),
+				),
 				meta.Meta,
 			),
 			goldmark.WithParserOptions(
@@ -156,7 +161,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
 
 		log.Warn("Unable to render markdown due to panic in goldmark: %v", err)
 		if log.IsDebug() {
-			log.Debug("Panic in markdown: %v\n%s", err, string(log.Stack(2)))
+			log.Debug("Panic in markdown: %v\n%s", err, log.Stack(2))
 		}
 	}()
 
@@ -167,7 +172,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
 		log.Error("Unable to ReadAll: %v", err)
 		return err
 	}
-	if err := converter.Convert(giteautil.NormalizeEOL(buf), lw, parser.WithContext(pc)); err != nil {
+	buf = giteautil.NormalizeEOL(buf)
+
+	rc := &RenderConfig{
+		Meta: "table",
+		Icon: "table",
+		Lang: "",
+	}
+	buf, _ = ExtractMetadataBytes(buf, rc)
+
+	pc.Set(renderConfigKey, rc)
+
+	if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
 		log.Error("Unable to render: %v", err)
 		return err
 	}
@@ -185,7 +201,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 
 		log.Warn("Unable to render markdown due to panic in goldmark - will return raw bytes")
 		if log.IsDebug() {
-			log.Debug("Panic in markdown: %v\n%s", err, string(log.Stack(2)))
+			log.Debug("Panic in markdown: %v\n%s", err, log.Stack(2))
 		}
 		_, err = io.Copy(output, input)
 		if err != nil {
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 732fe1a6beff9..fbb741d1cd8a7 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -6,6 +6,7 @@ package markdown_test
 
 import (
 	"context"
+	"os"
 	"strings"
 	"testing"
 
@@ -29,7 +30,7 @@ const (
 var localMetas = map[string]string{
 	"user":     "gogits",
 	"repo":     "gogs",
-	"repoPath": "../../../integrations/gitea-repositories-meta/user13/repo11.git/",
+	"repoPath": "../../../tests/gitea-repositories-meta/user13/repo11.git/",
 }
 
 func TestMain(m *testing.M) {
@@ -37,6 +38,12 @@ func TestMain(m *testing.M) {
 	if err := git.InitSimple(context.Background()); err != nil {
 		log.Fatal("git init failed, err: %v", err)
 	}
+	markup.Init(&markup.ProcessorHelper{
+		IsUsernameMentionable: func(ctx context.Context, username string) bool {
+			return username == "r-lyeh"
+		},
+	})
+	os.Exit(m.Run())
 }
 
 func TestRender_StandardLinks(t *testing.T) {
@@ -426,3 +433,106 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, expected, res)
 }
+
+func TestColorPreview(t *testing.T) {
+	const nl = "\n"
+	positiveTests := []struct {
+		testcase string
+		expected string
+	}{
+		{ // hex
+			"`#FF0000`",
+			`#FF0000
` + nl,
+		},
+		{ // rgb
+			"`rgb(16, 32, 64)`",
+			`rgb(16, 32, 64)
` + nl,
+		},
+		{ // short hex
+			"This is the color white `#000`",
+			`This is the color white #000
` + nl,
+		},
+		{ // hsl
+			"HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.",
+			`HSL stands for hue, saturation, and lightness. An example: hsl(0, 100%, 50%).
` + nl,
+		},
+		{ // uppercase hsl
+			"HSL stands for hue, saturation, and lightness. An example: `HSL(0, 100%, 50%)`.",
+			`HSL stands for hue, saturation, and lightness. An example: HSL(0, 100%, 50%).
` + nl,
+		},
+	}
+
+	for _, test := range positiveTests {
+		res, err := RenderString(&markup.RenderContext{}, test.testcase)
+		assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
+		assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+
+	}
+
+	negativeTests := []string{
+		// not a color code
+		"`FF0000`",
+		// inside a code block
+		"```javascript" + nl + `const red = "#FF0000";` + nl + "```",
+		// no backticks
+		"rgb(166, 32, 64)",
+		// typo
+		"`hsI(0, 100%, 50%)`",
+		// looks like a color but not really
+		"`hsl(40, 60, 80)`",
+	}
+
+	for _, test := range negativeTests {
+		res, err := RenderString(&markup.RenderContext{}, test)
+		assert.NoError(t, err, "Unexpected error in testcase: %q", test)
+		assert.NotContains(t, res, `a` + nl,
+		},
+		{
+			"$ a $",
+			`a
` + nl,
+		},
+		{
+			"$a$ $b$",
+			`a b
` + nl,
+		},
+		{
+			`\(a\) \(b\)`,
+			`a b
` + nl,
+		},
+		{
+			`$a a$b b$`,
+			`a a$b b
` + nl,
+		},
+		{
+			`a a$b b`,
+			`a a$b b
` + nl,
+		},
+		{
+			`a$b $a a$b b$`,
+			`a$b a a$b b
` + nl,
+		},
+		{
+			"$$a$$",
+			`a`)
+		r.writeLines(w, source, n)
+	} else {
+		_, _ = w.WriteString(``)
+		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
+			segment := c.(*ast.Text).Segment
+			value := util.EscapeHTML(segment.Value(source))
+			if bytes.HasSuffix(value, []byte("\n")) {
+				_, _ = w.Write(value[:len(value)-1])
+				if c != n.LastChild() {
+					_, _ = w.Write([]byte(" "))
+				}
+			} else {
+				_, _ = w.Write(value)
+			}
+		}
+		return ast.WalkSkipChildren, nil
+	}
+	_, _ = w.WriteString(``)
+	return ast.WalkContinue, nil
+}
+
+// RegisterFuncs registers the renderer for inline math nodes
+func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+	reg.Register(KindInline, r.renderInline)
+}
diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go
new file mode 100644
index 0000000000000..7854ac84dbb16
--- /dev/null
+++ b/modules/markup/markdown/math/math.go
@@ -0,0 +1,108 @@
+// 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 math
+
+import (
+	"github.com/yuin/goldmark"
+	"github.com/yuin/goldmark/parser"
+	"github.com/yuin/goldmark/renderer"
+	"github.com/yuin/goldmark/util"
+)
+
+// Extension is a math extension
+type Extension struct {
+	enabled           bool
+	parseDollarInline bool
+	parseDollarBlock  bool
+}
+
+// Option is the interface Options should implement
+type Option interface {
+	SetOption(e *Extension)
+}
+
+type extensionFunc func(e *Extension)
+
+func (fn extensionFunc) SetOption(e *Extension) {
+	fn(e)
+}
+
+// Enabled enables or disables this extension
+func Enabled(enable ...bool) Option {
+	value := true
+	if len(enable) > 0 {
+		value = enable[0]
+	}
+	return extensionFunc(func(e *Extension) {
+		e.enabled = value
+	})
+}
+
+// WithInlineDollarParser enables or disables the parsing of $...$
+func WithInlineDollarParser(enable ...bool) Option {
+	value := true
+	if len(enable) > 0 {
+		value = enable[0]
+	}
+	return extensionFunc(func(e *Extension) {
+		e.parseDollarInline = value
+	})
+}
+
+// WithBlockDollarParser enables or disables the parsing of $$...$$
+func WithBlockDollarParser(enable ...bool) Option {
+	value := true
+	if len(enable) > 0 {
+		value = enable[0]
+	}
+	return extensionFunc(func(e *Extension) {
+		e.parseDollarBlock = value
+	})
+}
+
+// Math represents a math extension with default rendered delimiters
+var Math = &Extension{
+	enabled:           true,
+	parseDollarBlock:  true,
+	parseDollarInline: true,
+}
+
+// NewExtension creates a new math extension with the provided options
+func NewExtension(opts ...Option) *Extension {
+	r := &Extension{
+		enabled:           true,
+		parseDollarBlock:  true,
+		parseDollarInline: true,
+	}
+
+	for _, o := range opts {
+		o.SetOption(r)
+	}
+	return r
+}
+
+// Extend extends goldmark with our parsers and renderers
+func (e *Extension) Extend(m goldmark.Markdown) {
+	if !e.enabled {
+		return
+	}
+
+	m.Parser().AddOptions(parser.WithBlockParsers(
+		util.Prioritized(NewBlockParser(e.parseDollarBlock), 701),
+	))
+
+	inlines := []util.PrioritizedValue{
+		util.Prioritized(NewInlineBracketParser(), 501),
+	}
+	if e.parseDollarInline {
+		inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 501))
+	}
+	m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
+
+	m.Renderer().AddOptions(renderer.WithNodeRenderers(
+		util.Prioritized(NewBlockRenderer(), 501),
+		util.Prioritized(NewInlineRenderer(), 502),
+	))
+}
diff --git a/modules/markup/markdown/meta.go b/modules/markup/markdown/meta.go
index faf92ae2c6b78..fb37236d77eb3 100644
--- a/modules/markup/markdown/meta.go
+++ b/modules/markup/markdown/meta.go
@@ -5,47 +5,100 @@
 package markdown
 
 import (
+	"bytes"
 	"errors"
-	"strings"
+	"unicode"
+	"unicode/utf8"
 
-	"gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
 )
 
-func isYAMLSeparator(line string) bool {
-	line = strings.TrimSpace(line)
-	for i := 0; i < len(line); i++ {
-		if line[i] != '-' {
+func isYAMLSeparator(line []byte) bool {
+	idx := 0
+	for ; idx < len(line); idx++ {
+		if line[idx] >= utf8.RuneSelf {
+			r, sz := utf8.DecodeRune(line[idx:])
+			if !unicode.IsSpace(r) {
+				return false
+			}
+			idx += sz
+			continue
+		}
+		if line[idx] != ' ' {
+			break
+		}
+	}
+	dashCount := 0
+	for ; idx < len(line); idx++ {
+		if line[idx] != '-' {
+			break
+		}
+		dashCount++
+	}
+	if dashCount < 3 {
+		return false
+	}
+	for ; idx < len(line); idx++ {
+		if line[idx] >= utf8.RuneSelf {
+			r, sz := utf8.DecodeRune(line[idx:])
+			if !unicode.IsSpace(r) {
+				return false
+			}
+			idx += sz
+			continue
+		}
+		if line[idx] != ' ' {
 			return false
 		}
 	}
-	return len(line) > 2
+	return true
 }
 
 // ExtractMetadata consumes a markdown file, parses YAML frontmatter,
 // and returns the frontmatter metadata separated from the markdown content
 func ExtractMetadata(contents string, out interface{}) (string, error) {
-	var front, body []string
-	lines := strings.Split(contents, "\n")
-	for idx, line := range lines {
-		if idx == 0 {
-			// First line has to be a separator
-			if !isYAMLSeparator(line) {
-				return "", errors.New("frontmatter must start with a separator line")
-			}
-			continue
+	body, err := ExtractMetadataBytes([]byte(contents), out)
+	return string(body), err
+}
+
+// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
+// and returns the frontmatter metadata separated from the markdown content
+func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) {
+	var front, body []byte
+
+	start, end := 0, len(contents)
+	idx := bytes.IndexByte(contents[start:], '\n')
+	if idx >= 0 {
+		end = start + idx
+	}
+	line := contents[start:end]
+
+	if !isYAMLSeparator(line) {
+		return contents, errors.New("frontmatter must start with a separator line")
+	}
+	frontMatterStart := end + 1
+	for start = frontMatterStart; start < len(contents); start = end + 1 {
+		end = len(contents)
+		idx := bytes.IndexByte(contents[start:], '\n')
+		if idx >= 0 {
+			end = start + idx
 		}
+		line := contents[start:end]
 		if isYAMLSeparator(line) {
-			front, body = lines[1:idx], lines[idx+1:]
+			front = contents[frontMatterStart:start]
+			if end+1 < len(contents) {
+				body = contents[end+1:]
+			}
 			break
 		}
 	}
 
 	if len(front) == 0 {
-		return "", errors.New("could not determine metadata")
+		return contents, errors.New("could not determine metadata")
 	}
 
-	if err := yaml.Unmarshal([]byte(strings.Join(front, "\n")), out); err != nil {
-		return "", err
+	if err := yaml.Unmarshal(front, out); err != nil {
+		return contents, err
 	}
-	return strings.Join(body, "\n"), nil
+	return body, nil
 }
diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go
index f525777a54c15..1e9768e618694 100644
--- a/modules/markup/markdown/meta_test.go
+++ b/modules/markup/markdown/meta_test.go
@@ -6,16 +6,31 @@ package markdown
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 
-	"code.gitea.io/gitea/modules/structs"
-
 	"github.com/stretchr/testify/assert"
 )
 
+/*
+IssueTemplate is a legacy to keep the unit tests working.
+Copied from structs.IssueTemplate, the original type has been changed a lot to support yaml template.
+*/
+type IssueTemplate struct {
+	Name   string   `json:"name" yaml:"name"`
+	Title  string   `json:"title" yaml:"title"`
+	About  string   `json:"about" yaml:"about"`
+	Labels []string `json:"labels" yaml:"labels"`
+	Ref    string   `json:"ref" yaml:"ref"`
+}
+
+func (it *IssueTemplate) Valid() bool {
+	return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
+}
+
 func TestExtractMetadata(t *testing.T) {
 	t.Run("ValidFrontAndBody", func(t *testing.T) {
-		var meta structs.IssueTemplate
+		var meta IssueTemplate
 		body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
 		assert.NoError(t, err)
 		assert.Equal(t, bodyTest, body)
@@ -24,19 +39,19 @@ func TestExtractMetadata(t *testing.T) {
 	})
 
 	t.Run("NoFirstSeparator", func(t *testing.T) {
-		var meta structs.IssueTemplate
+		var meta IssueTemplate
 		_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
 		assert.Error(t, err)
 	})
 
 	t.Run("NoLastSeparator", func(t *testing.T) {
-		var meta structs.IssueTemplate
+		var meta IssueTemplate
 		_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
 		assert.Error(t, err)
 	})
 
 	t.Run("NoBody", func(t *testing.T) {
-		var meta structs.IssueTemplate
+		var meta IssueTemplate
 		body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
 		assert.NoError(t, err)
 		assert.Equal(t, "", body)
@@ -45,6 +60,38 @@ func TestExtractMetadata(t *testing.T) {
 	})
 }
 
+func TestExtractMetadataBytes(t *testing.T) {
+	t.Run("ValidFrontAndBody", func(t *testing.T) {
+		var meta IssueTemplate
+		body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
+		assert.NoError(t, err)
+		assert.Equal(t, bodyTest, string(body))
+		assert.Equal(t, metaTest, meta)
+		assert.True(t, meta.Valid())
+	})
+
+	t.Run("NoFirstSeparator", func(t *testing.T) {
+		var meta IssueTemplate
+		_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
+		assert.Error(t, err)
+	})
+
+	t.Run("NoLastSeparator", func(t *testing.T) {
+		var meta IssueTemplate
+		_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
+		assert.Error(t, err)
+	})
+
+	t.Run("NoBody", func(t *testing.T) {
+		var meta IssueTemplate
+		body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
+		assert.NoError(t, err)
+		assert.Equal(t, "", string(body))
+		assert.Equal(t, metaTest, meta)
+		assert.True(t, meta.Valid())
+	})
+}
+
 var (
 	sepTest   = "-----"
 	frontTest = `name: Test
@@ -54,7 +101,7 @@ labels:
   - bug
   - "test label"`
 	bodyTest = "This is the body"
-	metaTest = structs.IssueTemplate{
+	metaTest = IssueTemplate{
 		Name:   "Test",
 		About:  "A Test",
 		Title:  "Test Title",
diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go
index bef67e9e59bf2..1ba75dbb68ec3 100644
--- a/modules/markup/markdown/renderconfig.go
+++ b/modules/markup/markdown/renderconfig.go
@@ -9,58 +9,53 @@ import (
 	"strings"
 
 	"github.com/yuin/goldmark/ast"
-	east "github.com/yuin/goldmark/extension/ast"
-	"gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
 )
 
 // RenderConfig represents rendering configuration for this file
 type RenderConfig struct {
-	Meta string
-	Icon string
-	TOC  bool
-	Lang string
+	Meta     string
+	Icon     string
+	TOC      bool
+	Lang     string
+	yamlNode *yaml.Node
 }
 
-// ToRenderConfig converts a yaml.MapSlice to a RenderConfig
-func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) {
-	if meta == nil {
-		return
-	}
-	found := false
-	var giteaMetaControl yaml.MapItem
-	for _, item := range meta {
-		strKey, ok := item.Key.(string)
-		if !ok {
-			continue
-		}
-		strKey = strings.TrimSpace(strings.ToLower(strKey))
-		switch strKey {
-		case "gitea":
-			giteaMetaControl = item
-			found = true
-		case "include_toc":
-			val, ok := item.Value.(bool)
-			if !ok {
-				continue
-			}
-			rc.TOC = val
-		case "lang":
-			val, ok := item.Value.(string)
-			if !ok {
-				continue
-			}
-			val = strings.TrimSpace(val)
-			if len(val) == 0 {
-				continue
-			}
-			rc.Lang = val
+// UnmarshalYAML implement yaml.v3 UnmarshalYAML
+func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
+	if rc == nil {
+		rc = &RenderConfig{
+			Meta: "table",
+			Icon: "table",
+			Lang: "",
 		}
 	}
+	rc.yamlNode = value
+
+	type commonRenderConfig struct {
+		TOC  bool   `yaml:"include_toc"`
+		Lang string `yaml:"lang"`
+	}
+	var basic commonRenderConfig
+	if err := value.Decode(&basic); err != nil {
+		return fmt.Errorf("unable to decode into commonRenderConfig %w", err)
+	}
+
+	if basic.Lang != "" {
+		rc.Lang = basic.Lang
+	}
+
+	rc.TOC = basic.TOC
+
+	type controlStringRenderConfig struct {
+		Gitea string `yaml:"gitea"`
+	}
+
+	var stringBasic controlStringRenderConfig
 
-	if found {
-		switch v := giteaMetaControl.Value.(type) {
-		case string:
-			switch v {
+	if err := value.Decode(&stringBasic); err == nil {
+		if stringBasic.Gitea != "" {
+			switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) {
 			case "none":
 				rc.Meta = "none"
 			case "table":
@@ -68,96 +63,65 @@ func (rc *RenderConfig) ToRenderConfig(meta yaml.MapSlice) {
 			default: // "details"
 				rc.Meta = "details"
 			}
-		case yaml.MapSlice:
-			for _, item := range v {
-				strKey, ok := item.Key.(string)
-				if !ok {
-					continue
-				}
-				strKey = strings.TrimSpace(strings.ToLower(strKey))
-				switch strKey {
-				case "meta":
-					val, ok := item.Value.(string)
-					if !ok {
-						continue
-					}
-					switch strings.TrimSpace(strings.ToLower(val)) {
-					case "none":
-						rc.Meta = "none"
-					case "table":
-						rc.Meta = "table"
-					default: // "details"
-						rc.Meta = "details"
-					}
-				case "details_icon":
-					val, ok := item.Value.(string)
-					if !ok {
-						continue
-					}
-					rc.Icon = strings.TrimSpace(strings.ToLower(val))
-				case "include_toc":
-					val, ok := item.Value.(bool)
-					if !ok {
-						continue
-					}
-					rc.TOC = val
-				case "lang":
-					val, ok := item.Value.(string)
-					if !ok {
-						continue
-					}
-					val = strings.TrimSpace(val)
-					if len(val) == 0 {
-						continue
-					}
-					rc.Lang = val
-				}
-			}
 		}
+		return nil
 	}
-}
 
-func (rc *RenderConfig) toMetaNode(meta yaml.MapSlice) ast.Node {
-	switch rc.Meta {
-	case "table":
-		return metaToTable(meta)
-	case "details":
-		return metaToDetails(meta, rc.Icon)
-	default:
+	type giteaControl struct {
+		Meta *string `yaml:"meta"`
+		Icon *string `yaml:"details_icon"`
+		TOC  *bool   `yaml:"include_toc"`
+		Lang *string `yaml:"lang"`
+	}
+
+	type complexGiteaConfig struct {
+		Gitea *giteaControl `yaml:"gitea"`
+	}
+	var complex complexGiteaConfig
+	if err := value.Decode(&complex); err != nil {
+		return fmt.Errorf("unable to decode into complexRenderConfig %w", err)
+	}
+
+	if complex.Gitea == nil {
 		return nil
 	}
-}
 
-func metaToTable(meta yaml.MapSlice) ast.Node {
-	table := east.NewTable()
-	alignments := []east.Alignment{}
-	for range meta {
-		alignments = append(alignments, east.AlignNone)
+	if complex.Gitea.Meta != nil {
+		switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) {
+		case "none":
+			rc.Meta = "none"
+		case "table":
+			rc.Meta = "table"
+		default: // "details"
+			rc.Meta = "details"
+		}
 	}
-	row := east.NewTableRow(alignments)
-	for _, item := range meta {
-		cell := east.NewTableCell()
-		cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Key))))
-		row.AppendChild(row, cell)
+
+	if complex.Gitea.Icon != nil {
+		rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon))
 	}
-	table.AppendChild(table, east.NewTableHeader(row))
 
-	row = east.NewTableRow(alignments)
-	for _, item := range meta {
-		cell := east.NewTableCell()
-		cell.AppendChild(cell, ast.NewString([]byte(fmt.Sprintf("%v", item.Value))))
-		row.AppendChild(row, cell)
+	if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" {
+		rc.Lang = *complex.Gitea.Lang
+	}
+
+	if complex.Gitea.TOC != nil {
+		rc.TOC = *complex.Gitea.TOC
 	}
-	table.AppendChild(table, row)
-	return table
-}
 
-func metaToDetails(meta yaml.MapSlice, icon string) ast.Node {
-	details := NewDetails()
-	summary := NewSummary()
-	summary.AppendChild(summary, NewIcon(icon))
-	details.AppendChild(details, summary)
-	details.AppendChild(details, metaToTable(meta))
+	return nil
+}
 
-	return details
+func (rc *RenderConfig) toMetaNode() ast.Node {
+	if rc.yamlNode == nil {
+		return nil
+	}
+	switch rc.Meta {
+	case "table":
+		return nodeToTable(rc.yamlNode)
+	case "details":
+		return nodeToDetails(rc.yamlNode, rc.Icon)
+	default:
+		return nil
+	}
 }
diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go
new file mode 100644
index 0000000000000..672edbf46d321
--- /dev/null
+++ b/modules/markup/markdown/renderconfig_test.go
@@ -0,0 +1,163 @@
+// 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 markdown
+
+import (
+	"strings"
+	"testing"
+
+	"gopkg.in/yaml.v3"
+)
+
+func TestRenderConfig_UnmarshalYAML(t *testing.T) {
+	tests := []struct {
+		name     string
+		expected *RenderConfig
+		args     string
+	}{
+		{
+			"empty", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "",
+			}, "",
+		},
+		{
+			"lang", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "test",
+			}, "lang: test",
+		},
+		{
+			"metatable", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "",
+			}, "gitea: table",
+		},
+		{
+			"metanone", &RenderConfig{
+				Meta: "none",
+				Icon: "table",
+				Lang: "",
+			}, "gitea: none",
+		},
+		{
+			"metadetails", &RenderConfig{
+				Meta: "details",
+				Icon: "table",
+				Lang: "",
+			}, "gitea: details",
+		},
+		{
+			"metawrong", &RenderConfig{
+				Meta: "details",
+				Icon: "table",
+				Lang: "",
+			}, "gitea: wrong",
+		},
+		{
+			"toc", &RenderConfig{
+				TOC:  true,
+				Meta: "table",
+				Icon: "table",
+				Lang: "",
+			}, "include_toc: true",
+		},
+		{
+			"tocfalse", &RenderConfig{
+				TOC:  false,
+				Meta: "table",
+				Icon: "table",
+				Lang: "",
+			}, "include_toc: false",
+		},
+		{
+			"toclang", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				TOC:  true,
+				Lang: "testlang",
+			}, `
+				include_toc: true
+				lang: testlang
+				`,
+		},
+		{
+			"complexlang", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "testlang",
+			}, `
+				gitea:
+					lang: testlang
+				`,
+		},
+		{
+			"complexlang2", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "testlang",
+			}, `
+	lang: notright
+	gitea:
+		lang: testlang
+`,
+		},
+		{
+			"complexlang", &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "testlang",
+			}, `
+	gitea:
+		lang: testlang
+`,
+		},
+		{
+			"complex2", &RenderConfig{
+				Lang: "two",
+				Meta: "table",
+				TOC:  true,
+				Icon: "smiley",
+			}, `
+	lang: one
+	include_toc: true
+	gitea:
+		details_icon: smiley
+		meta: table
+		include_toc: true
+		lang: two
+`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := &RenderConfig{
+				Meta: "table",
+				Icon: "table",
+				Lang: "",
+			}
+			if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", "    ")), got); err != nil {
+				t.Errorf("RenderConfig.UnmarshalYAML() error = %v\n%q", err, tt.args)
+				return
+			}
+
+			if got.Meta != tt.expected.Meta {
+				t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta)
+			}
+			if got.Icon != tt.expected.Icon {
+				t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
+			}
+			if got.Lang != tt.expected.Lang {
+				t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
+			}
+			if got.TOC != tt.expected.TOC {
+				t.Errorf("TOC Expected %t Got %t", tt.expected.TOC, got.TOC)
+			}
+		})
+	}
+}
diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go
index fec45103e5889..103894d1abfc1 100644
--- a/modules/markup/markdown/toc.go
+++ b/modules/markup/markdown/toc.go
@@ -9,7 +9,7 @@ import (
 	"net/url"
 
 	"code.gitea.io/gitea/modules/markup"
-	"code.gitea.io/gitea/modules/translation/i18n"
+	"code.gitea.io/gitea/modules/translation"
 
 	"github.com/yuin/goldmark/ast"
 )
@@ -18,7 +18,7 @@ func createTOCNode(toc []markup.Header, lang string) ast.Node {
 	details := NewDetails()
 	summary := NewSummary()
 
-	summary.AppendChild(summary, ast.NewString([]byte(i18n.Tr(lang, "toc"))))
+	summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc"))))
 	details.AppendChild(details, summary)
 	ul := ast.NewList('-')
 	details.AppendChild(details, ul)
diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go
index 64079194fff78..c7f8ee69f19c9 100644
--- a/modules/markup/mdstripper/mdstripper.go
+++ b/modules/markup/mdstripper/mdstripper.go
@@ -141,7 +141,7 @@ func (r *stripRenderer) AddOptions(...renderer.Option) {
 }
 
 // StripMarkdown parses markdown content by removing all markup and code blocks
-//	in order to extract links and other references
+// in order to extract links and other references
 func StripMarkdown(rawBytes []byte) (string, []string) {
 	buf, links := StripMarkdownBytes(rawBytes)
 	return string(buf), links
@@ -153,7 +153,7 @@ var (
 )
 
 // StripMarkdownBytes parses markdown content by removing all markup and code blocks
-//	in order to extract links and other references
+// in order to extract links and other references
 func StripMarkdownBytes(rawBytes []byte) ([]byte, []string) {
 	once.Do(func() {
 		gdMarkdown := goldmark.New(
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index 8c9f3b3da736b..abbff1e435e83 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -17,8 +17,8 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
 
-	"github.com/alecthomas/chroma"
-	"github.com/alecthomas/chroma/lexers"
+	"github.com/alecthomas/chroma/v2"
+	"github.com/alecthomas/chroma/v2/lexers"
 	"github.com/niklasfasching/go-org/org"
 )
 
@@ -75,7 +75,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 
 		if lexer == nil {
 			// include language-x class as part of commonmark spec
-			if _, err := w.WriteString(``); err != nil {
+			if _, err := w.WriteString(``); err != nil {
 				return ""
 			}
 			if _, err := w.WriteString(html.EscapeString(source)); err != nil {
@@ -83,7 +83,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 			}
 		} else {
 			// include language-x class as part of commonmark spec
-			if _, err := w.WriteString(``); err != nil {
+			if _, err := w.WriteString(``); err != nil {
 				return ""
 			}
 			lexer = chroma.Coalesce(lexer)
@@ -110,7 +110,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 
 	res, err := org.New().Silent().Parse(input, "").Write(w)
 	if err != nil {
-		return fmt.Errorf("orgmode.Render failed: %v", err)
+		return fmt.Errorf("orgmode.Render failed: %w", err)
 	}
 	_, err = io.Copy(output, strings.NewReader(res))
 	return err
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index 4fc0a20db2b28..cd12cd8c171a1 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -79,9 +79,9 @@ func HelloWorld() {
 }
 #+end_src
 `, `
-
// HelloWorld prints "Hello World"
- func  HelloWorld ()  { 
-	fmt . Println ( "Hello World" ) 
- } 
+
// HelloWorld prints "Hello World"
+ func  HelloWorld ()  { 
+	fmt . Println ( "Hello World" ) 
+} NAUGHTY `, `NAUGHTY `,
 		`contents `, `contents `,
+
+		// Color property
+		`Hello World `, `Hello World `,
+		`Hello World
`, `Hello World
`,
+		`Hello World`, `Hello World`,
+		`Hello World `, `Hello World `,
+		`Hello World
`, `Hello World
`,
+		`Hello World`, `Hello World`,
 	}
 
 	for i := 0; i < len(testCases); i += 2 {
@@ -55,7 +63,7 @@ func Test_Sanitizer(t *testing.T) {
 func TestSanitizeNonEscape(t *testing.T) {
 	descStr := "<script>alert(document.domain)</script> "
 
-	output := template.HTML(Sanitize(string(descStr)))
+	output := template.HTML(Sanitize(descStr))
 	if strings.Contains(string(output), "
+		
 	{{end}}
 	{{if eq .CaptchaType "hcaptcha"}}
 		
 	{{end}}
 {{end}}
-	
+	
 {{template "custom/footer" .}}
 
 
diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl
index f5ec6b0bf3419..89be609225895 100644
--- a/templates/base/footer_content.tmpl
+++ b/templates/base/footer_content.tmpl
@@ -1,9 +1,9 @@
 
 	
 		
-			{{.i18n.Tr "powered_by" "Gitea"}}
+			
{{.locale.Tr "powered_by" "Gitea"}} 
 			{{if (or .ShowFooterVersion .PageIsAdmin)}}
-				{{.i18n.Tr "version"}}:
+				{{.locale.Tr "version"}}:
 				{{if .IsAdmin}}
 					
{{AppVer}} 
 				{{else}}
@@ -11,9 +11,8 @@
 				{{end}}
 			{{end}}
 			{{if and .TemplateLoadTimes ShowFooterTemplateLoadTime}}
-				{{.i18n.Tr "page"}}: 
{{LoadTimes .PageStartTime}} 
-				{{.i18n.Tr "template"}}
-				{{if .TemplateName}} {{.TemplateName}}{{end}}: 
{{call .TemplateLoadTimes}} 
+				{{.locale.Tr "page"}}: 
{{LoadTimes .PageStartTime}} 
+				{{.locale.Tr "template"}}{{if .TemplateName}} {{.TemplateName}}{{end}}: 
{{call .TemplateLoadTimes}} 
 			{{end}}
 		
 
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index e75531746a42c..70a88ff75569a 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -1,9 +1,9 @@
 
-
+
 
 	{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} 
+	{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} 
 	
 		
diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue
index 75fbceb8007a7..bfe05628e84aa 100644
--- a/web_src/js/components/PullRequestMergeForm.vue
+++ b/web_src/js/components/PullRequestMergeForm.vue
@@ -1,7 +1,7 @@
 
   
-