Compare commits

..

1 Commits

Author SHA1 Message Date
techknowlogick 49a4e4555a [skip ci] Updated translations via Crowdin 2022-10-26 00:20:58 +00:00
98 changed files with 593 additions and 1273 deletions
-176
View File
@@ -4,182 +4,6 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.18.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.18.0-rc1) - 2022-11-15
* BREAKING
* Remove U2F support (#20141)
* FEATURES
* Add color previews in markdown (#21474)
* Allow package version sorting (#21453)
* Add support for Chocolatey/NuGet v2 API (#21393)
* Add API endpoint to get changed files of a PR (#21177)
* Add filetree on left of diff view (#21012)
* Support Issue forms and PR forms (#20987)
* Add support for Vagrant packages (#20930)
* Add support for `npm unpublish` (#20688)
* Add badge capabilities to users (#20607)
* Add issue filter for Author (#20578)
* Add KaTeX rendering to Markdown. (#20571)
* Add support for Pub packages (#20560)
* Support localized README (#20508)
* Add support mCaptcha as captcha provider (#20458)
* Add team member invite by email (#20307)
* Added email notification option to receive all own messages (#20179)
* Switch Unicode Escaping to a VSCode-like system (#19990)
* Add user/organization code search (#19977)
* Only show relevant repositories on explore page (#19361)
* User keypairs and HTTP signatures for ActivityPub federation using go-ap (#19133)
* Add sitemap support (#18407)
* Allow creation of OAuth2 applications for orgs (#18084)
* Add system setting table with cache and also add cache supports for user setting (#18058)
* Add pages to view watched repos and subscribed issues/PRs (#17156)
* Support Proxy protocol (#12527)
* Implement sync push mirror on commit (#19411)
* API
* Make external issue tracker regexp configurable via API (#21338)
* Add name field for org api (#21270)
* Show teams with no members if user is admin (#21204)
* Add latest commit's SHA to content response (#20398)
* Add allow_rebase_update, default_delete_branch_after_merge to repository api response (#20079)
* Add new endpoints for push mirrors management (#19841)
* ENHANCEMENTS
* Use CSS color-scheme instead of invert (#21616) (#21623)
* Respect user's locale when rendering the date range in the repo activity page (#21410)
* Change `commits-table` column width (#21564)
* Refactor git command arguments and make all arguments to be safe to be used (#21535)
* CSS color enhancements (#21534)
* Add link to user profile in markdown mention only if user exists (#21533, #21554)
* Add option to skip index dirs (#21501)
* Diff file tree tweaks (#21446)
* Localize all timestamps (#21440)
* Add `code` highlighting in issue titles (#21432)
* Use Name instead of DisplayName in LFS Lock (#21415)
* Consolidate more CSS colors into variables (#21402)
* Redirect to new repository owner (#21398)
* Use ISO date format instead of hard-coded English date format for date range in repo activity page (#21396)
* Use weighted algorithm for string matching when finding files in repo (#21370)
* Show private data in feeds (#21369)
* Refactor parseTreeEntries, speed up tree list (#21368)
* Add GET and DELETE endpoints for Docker blob uploads (#21367)
* Add nicer error handling on template compile errors (#21350)
* Add `stat` to `ToCommit` function for speed (#21337)
* Support instance-wide OAuth2 applications (#21335)
* Record OAuth client type at registration (#21316)
* Add new CSS variables --color-accent and --color-small-accent (#21305)
* Improve error descriptions for unauthorized_client (#21292)
* Case-insensitive "find files in repo" (#21269)
* Consolidate more CSS rules, fix inline code on arc-green (#21260)
* Log real ip of requests from ssh (#21216)
* Save files in local storage as group readable (#21198)
* Enable fluid page layout on medium size viewports (#21178)
* File header tweaks (#21175)
* Added missing headers on user packages page (#21172)
* Display image digest for container packages (#21170)
* Skip dirty check for team forms (#21154)
* Keep path when creating a new branch (#21153)
* Remove fomantic image module (#21145)
* Make labels clickable in the comments section. (#21137)
* Sort branches and tags by date descending (#21136)
* Better repo API unit checks (#21130)
* Improve commit status icons (#21124)
* Limit length of repo description and repo url input fields (#21119)
* Show .editorconfig errors in frontend (#21088)
* Allow poster to choose reviewers (#21084)
* Remove black labels and CSS cleanup (#21003)
* Make e-mail sanity check more precise (#20991)
* Use native inputs in whitespace dropdown (#20980)
* Enhance package date display (#20928)
* Display total blob size of a package version (#20927)
* Show language name on hover (#20923)
* Show instructions for all generic package files (#20917)
* Refactor AssertExistsAndLoadBean to use generics (#20797)
* Move the official website link at the footer of gitea (#20777)
* Add support for full name in reverse proxy auth (#20776)
* Remove useless JS operation for relative time tooltips (#20756)
* Replace some icons with SVG (#20741)
* Change commit status icons to SVG (#20736)
* Improve single repo action for issue and pull requests (#20730)
* Allow multiple files in generic packages (#20661)
* Add option to create new issue from /issues page (#20650)
* Background color of private list-items updated (#20630)
* Added search input field to issue filter (#20623)
* Increase default item listing size `ISSUE_PAGING_NUM` to 20 (#20547)
* Modify milestone search keywords to be case insensitive again (#20513)
* Show hint to link package to repo when viewing empty repo package list (#20504)
* Add Tar ZSTD support (#20493)
* Make code review checkboxes clickable (#20481)
* Add "X-Gitea-Object-Type" header for GET `/raw/` & `/media/` API (#20438)
* Display project in issue list (#20434)
* Prepend commit message to template content when opening a new PR (#20429)
* Replace fomantic popup module with tippy.js (#20428)
* Allow to specify colors for text in markup (#20363)
* Allow access to the Public Organization Member lists with minimal permissions (#20330)
* Use default values when provided values are empty (#20318)
* Vertical align navbar avatar at middle (#20302)
* Delete cancel button in repo creation page (#21381)
* Include login_name in adminCreateUser response (#20283)
* fix: icon margin in user/settings/repos (#20281)
* Remove blue text on migrate page (#20273)
* Modify milestone search keywords to be case insensitive (#20266)
* Move some files into models' sub packages (#20262)
* Add tooltip to repo icons in explore page (#20241)
* Remove deprecated licenses (#20222)
* Webhook for Wiki changes (#20219)
* Share HTML template renderers and create a watcher framework (#20218)
* Allow enable LDAP source and disable user sync via CLI (#20206)
* Adds a checkbox to select all issues/PRs (#20177)
* Refactor `i18n` to `locale` (#20153)
* Disable status checks in template if none found (#20088)
* Allow manager logging to set SQL (#20064)
* Add order by for assignee no sort issue (#20053)
* Take a stab at porting existing components to Vue3 (#20044)
* Add doctor command to write commit-graphs (#20007)
* Add support for authentication based on reverse proxy email (#19949)
* Enable spellcheck for EasyMDE, use contenteditable mode (#19776)
* Allow specifying SECRET_KEY_URI, similar to INTERNAL_TOKEN_URI (#19663)
* Rework mailer settings (#18982)
* Add option to purge users (#18064)
* Add author search input (#21246)
* Make rss/atom identifier globally unique (#21550)
* BUGFIXES
* Prevent panic in doctor command when running default checks (#21791) (#21807)
* Load GitRepo in API before deleting issue (#21720) (#21796)
* Ignore line anchor links with leading zeroes (#21728) (#21776)
* Set last login when activating account (#21731) (#21755)
* Fix UI language switching bug (#21597) (#21749)
* Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
* Allow local package identifiers for PyPI packages (#21690) (#21727)
* Deal with markdown template without metadata (#21639) (#21654)
* Fix opaque background on mermaid diagrams (#21642) (#21652)
* Fix repository adoption on Windows (#21646) (#21650)
* Sync git hooks when config file path changed (#21619) (#21626)
* Fix 500 on PR files API (#21602) (#21607)
* Fix `Timestamp.IsZero` (#21593) (#21603)
* Fix viewing user subscriptions (#21482)
* Fix mermaid-related bugs (#21431)
* Fix branch dropdown shifting on page load (#21428)
* Fix default theme-auto selector when nologin (#21346)
* Fix and improve incorrect error messages (#21342)
* Fix formatted link for PR review notifications to matrix (#21319)
* Center-aligning content of WebAuthN page (#21127)
* Remove follow from commits by file (#20765)
* Fix commit status popup (#20737)
* Fix init mail render logic (#20704)
* Use correct page size for link header pagination (#20546)
* Preserve unix socket file (#20499)
* Use tippy.js for context popup (#20393)
* Add missing parameter for error in log message (#20144)
* Do not allow organisation owners add themselves as collaborator (#20043)
* Rework file highlight rendering and fix yaml copy-paste (#19967)
* Improve code diff highlight, fix incorrect rendered diff result (#19958)
* TESTING
* Improve OAuth integration tests (#21390)
* Add playwright tests (#20123)
* BUILD
* Switch to building with go1.19 (#20695)
* Update JS dependencies, adjust eslint (#20659)
* Add more linters to improve code readability (#19989)
## [1.17.3](https://github.com/go-gitea/gitea/releases/tag/v1.17.3) - 2022-10-15
* SECURITY
+4 -4
View File
@@ -413,9 +413,9 @@ var (
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
cli.StringFlag{
Name: "host",
Name: "addr",
Value: "",
Usage: "SMTP Host",
Usage: "SMTP Addr",
},
cli.IntFlag{
Name: "port",
@@ -955,8 +955,8 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
}
conf.Auth = c.String("auth-type")
}
if c.IsSet("host") {
conf.Host = c.String("host")
if c.IsSet("addr") {
conf.Addr = c.String("addr")
}
if c.IsSet("port") {
conf.Port = c.Int("port")
+4 -4
View File
@@ -94,11 +94,11 @@ require (
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.2.1-0.20221112162523-6fad3dfc1891
golang.org/x/net v0.2.0
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.0.0-20220927171203-f486391704dc
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/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/text v0.3.8
golang.org/x/tools v0.1.12
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
+9 -9
View File
@@ -1608,8 +1608,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0
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/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@@ -1721,8 +1721,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
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/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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=
@@ -1876,13 +1876,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
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/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
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=
@@ -1892,8 +1892,8 @@ 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/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/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
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=
+6 -13
View File
@@ -20,12 +20,8 @@ import (
"code.gitea.io/gitea/modules/setting"
)
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
)
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
const DefaultAvatarPixelSize = 28
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
type EmailHash struct {
@@ -154,11 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink()
}
enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
var err error
if enableFederatedAvatar && system_model.LibravatarService != nil {
if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email)
if final {
// for final link, we can spend more time on slow external query
@@ -176,10 +171,8 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return urlStr
}
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
disableGravatar := disableGravatarSetting.GetValueBool()
if !disableGravatar {
disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatar != nil && !disableGravatar.GetValueBool() {
// copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
-8
View File
@@ -14,7 +14,6 @@ import (
"regexp"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -514,13 +513,6 @@ 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())
+2 -3
View File
@@ -458,9 +458,8 @@ 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 {
// If user is nil, it's an anonymous user/request.
// The Ghost user is handled like an anonymous user.
if user == nil || user.IsGhost() {
// Not SignedUser
if user == nil {
return orgOrUser.Visibility == structs.VisibleTypePublic
}
+6 -34
View File
@@ -12,7 +12,6 @@ import (
"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"
@@ -36,10 +35,6 @@ func (s *Setting) TableName() string {
}
func (s *Setting) GetValueBool() bool {
if s == nil {
return false
}
b, _ := strconv.ParseBool(s.SettingValue)
return b
}
@@ -80,8 +75,8 @@ func IsErrDataExpired(err error) bool {
return ok
}
// GetSettingNoCache returns specific setting without using the cache
func GetSettingNoCache(key string) (*Setting, error) {
// GetSetting returns specific setting
func GetSetting(key string) (*Setting, error) {
v, err := GetSettings([]string{key})
if err != nil {
return nil, err
@@ -92,24 +87,6 @@ func GetSettingNoCache(key string) (*Setting, error) {
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++ {
@@ -162,13 +139,12 @@ func GetAllSettings() (AllSettings, error) {
// 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)
s, err := GetSetting(key)
if IsErrSettingIsNotExist(err) {
return SetSetting(&Setting{
SettingKey: key,
@@ -184,13 +160,9 @@ func SetSettingNoVersion(key, value string) error {
// 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 {
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
return err
}
setting.Version++
return nil
}
@@ -241,7 +213,7 @@ var (
func Init() error {
var disableGravatar bool
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
if IsErrSettingIsNotExist(err) {
disableGravatar = setting.GetDefaultDisableGravatar()
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
@@ -252,7 +224,7 @@ func Init() error {
}
var enableFederatedAvatar bool
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
if IsErrSettingIsNotExist(err) {
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
-5
View File
@@ -9,8 +9,3 @@ 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
}
-1
View File
@@ -89,7 +89,6 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
&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: %w", err)
}
+4 -2
View File
@@ -68,9 +68,11 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false
autoGenerateAvatar := false
var disableGravatar bool
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
disableGravatar := disableGravatarSetting.GetValueBool()
if disableGravatarSetting != nil {
disableGravatar = disableGravatarSetting.GetValueBool()
}
switch {
case u.UseCustomAvatar:
+5 -31
View File
@@ -10,7 +10,6 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/cache"
"xorm.io/builder"
)
@@ -48,25 +47,9 @@ func IsErrUserSettingIsNotExist(err error) bool {
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
// GetSetting returns specific setting
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})
v, err := GetUserSettings(uid, []string{key})
if err != nil {
return nil, err
}
@@ -76,8 +59,8 @@ func GetSettingNoCache(uid int64, key string) (*Setting, error) {
return v[key], nil
}
// GetSettings returns specific settings from user
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
// GetUserSettings returns specific settings from user
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
settings := make([]*Setting, 0, len(keys))
if err := db.GetEngine(db.DefaultContext).
Where("user_id=?", uid).
@@ -122,7 +105,6 @@ 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 {
@@ -142,10 +124,7 @@ 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
}
@@ -154,12 +133,7 @@ func SetUserSetting(userID int64, key, value string) error {
if err := validateUserSettingKey(key); err != nil {
return err
}
_, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
return value, upsertUserSettingValue(userID, key, value)
})
return err
return upsertUserSettingValue(userID, key, value)
}
func upsertUserSettingValue(userID int64, key, value string) error {
+1 -1
View File
@@ -27,7 +27,7 @@ func TestSettings(t *testing.T) {
assert.NoError(t, err)
// get specific setting
settings, err := user_model.GetSettings(99, []string{keyName})
settings, err := user_model.GetUserSettings(99, []string{keyName})
assert.NoError(t, err)
assert.Len(t, settings, 1)
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// 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})
settings, err = user_model.GetUserSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
if err != nil {
return
} else if len(settings) == 0 {
+48 -87
View File
@@ -46,64 +46,32 @@ 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()
}
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
if !conn.IsExist(key) {
var (
value string
err error
)
if value, err = getFunc(); err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return "", err
}
}
if value, ok := cached.(string); ok {
return value, nil
value := conn.Get(key)
if v, ok := value.(string); ok {
return v, nil
}
if stringer, ok := cached.(fmt.Stringer); ok {
return stringer.String(), nil
if v, ok := value.(fmt.Stringer); ok {
return v.String(), nil
}
return fmt.Sprintf("%s", cached), nil
return fmt.Sprintf("%s", conn.Get(key)), nil
}
// GetInt returns key value from cache with callback when no key exists in cache
@@ -111,33 +79,30 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
if !conn.IsExist(key) {
var (
value int
err error
)
if value, err = getFunc(); err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
switch v := cached.(type) {
case int:
return v, nil
case string:
value, err := strconv.Atoi(v)
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return 0, err
}
}
switch value := conn.Get(key).(type) {
case int:
return value, nil
default:
value, err := getFunc()
case string:
v, err := strconv.Atoi(value)
if err != nil {
return value, err
return 0, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
return v, nil
default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
}
}
@@ -146,34 +111,30 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
if !conn.IsExist(key) {
var (
value int64
err error
)
if value, err = getFunc(); err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
switch v := conn.Get(key).(type) {
case int64:
return v, nil
case string:
value, err := strconv.ParseInt(v, 10, 64)
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return 0, err
}
}
switch value := conn.Get(key).(type) {
case int64:
return value, nil
default:
value, err := getFunc()
case string:
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return value, err
return 0, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
return v, nil
default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
}
}
+23 -43
View File
@@ -34,7 +34,6 @@ import (
"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"
@@ -323,9 +322,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)
}
@@ -346,55 +345,36 @@ func (ctx *Context) RespHeader() http.Header {
return ctx.Resp.Header()
}
type ServeHeaderOptions struct {
ContentType string // defaults to "application/octet-stream"
ContentTypeCharset string
Disposition string // defaults to "attachment"
Filename string
CacheDuration time.Duration // defaults to 5 minutes
}
// 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
}
}
header.Set("Content-Type", contentType)
header.Set("X-Content-Type-Options", "nosniff")
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)
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")
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
ctx.SetServeHeaders(&ServeHeaderOptions{
Filename: name,
})
ctx.SetServeHeaders(name)
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
}
// 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)
}
ctx.SetServeHeaders(name)
http.ServeFile(ctx.Resp, ctx.Req, file)
}
// 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) {
-3
View File
@@ -205,9 +205,6 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
// find stopwatches without existing issue
genericOrphanCheck("Orphaned Stopwatches without existing Issue",
"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 {
+6 -4
View File
@@ -19,9 +19,11 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
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()})
runOpts := &git.RunOpts{Dir: repo.RepoPath()}
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).RunStdString(runOpts)
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(runOpts)
// what we expect: default branch is valid, and HEAD points to it
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
@@ -47,7 +49,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
}
// otherwise, let's try fixing HEAD
err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", repo.DefaultBranch).Run(runOpts)
if err != nil {
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
return nil
@@ -63,7 +65,7 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
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)
logger.Info("All %d repos have their HEADs in the correct state")
} else {
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)
+6 -20
View File
@@ -202,11 +202,8 @@ func (c *Command) Run(opts *RunOpts) error {
if opts == nil {
opts = &RunOpts{}
}
// We must not change the provided options
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultCommandExecutionTimeout
if opts.Timeout <= 0 {
opts.Timeout = defaultCommandExecutionTimeout
}
if len(opts.Dir) == 0 {
@@ -241,7 +238,7 @@ func (c *Command) Run(opts *RunOpts) error {
if opts.UseContextTimeout {
ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
} else {
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, opts.Timeout, desc)
}
defer finished()
@@ -342,20 +339,9 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
}
stdoutBuf := &bytes.Buffer{}
stderrBuf := &bytes.Buffer{}
// 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)
opts.Stdout = stdoutBuf
opts.Stderr = stderrBuf
err := c.Run(opts)
stderr = stderrBuf.Bytes()
if err != nil {
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
+2 -2
View File
@@ -165,7 +165,7 @@ func validateOptions(field *api.IssueFormField, idx int) error {
return position.Errorf("should be a string")
}
case api.IssueFormFieldTypeCheckboxes:
opt, ok := option.(map[string]interface{})
opt, ok := option.(map[interface{}]interface{})
if !ok {
return position.Errorf("should be a dictionary")
}
@@ -351,7 +351,7 @@ func (o *valuedOption) Label() string {
return label
}
case api.IssueFormFieldTypeCheckboxes:
if vs, ok := o.data.(map[string]interface{}); ok {
if vs, ok := o.data.(map[interface{}]interface{}); ok {
if v, ok := vs["label"].(string); ok {
return v
}
+102 -224
View File
@@ -6,21 +6,18 @@ package template
import (
"net/url"
"reflect"
"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 string
content string
wantErr string
}{
{
name: "miss name",
@@ -319,9 +316,21 @@ body:
`,
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
},
{
name: "valid",
content: `
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl, err := unmarshal("test.yaml", []byte(tt.content))
if err != nil {
t.Fatal(err)
}
if err := Validate(tmpl); (err == nil) != (tt.wantErr == "") || err != nil && err.Error() != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %q", err, tt.wantErr)
}
})
}
t.Run("valid", func(t *testing.T) {
content := `
name: Name
title: Title
about: About
@@ -377,227 +386,96 @@ body:
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",
`
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",
},
},
{
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,
},
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},
},
},
{
Type: "checkboxes",
ID: "id5",
Attributes: map[string]interface{}{
"label": "Label of checkboxes",
"description": "Description of checkboxes",
"options": []interface{}{
map[interface{}]interface{}{"label": "Option 1 of checkboxes", "required": true},
map[interface{}]interface{}{"label": "Option 2 of checkboxes", "required": false},
map[interface{}]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))
}
})
}
FileName: "test.yaml",
}
got, err := unmarshal("test.yaml", []byte(content))
if err != nil {
t.Fatal(err)
}
if err := Validate(got); err != nil {
t.Errorf("Validate() error = %v", err)
}
if !reflect.DeepEqual(want, got) {
jsonWant, _ := json.Marshal(want)
jsonGot, _ := json.Marshal(got)
t.Errorf("want:\n%s\ngot:\n%s", jsonWant, jsonGot)
}
})
}
func TestRenderToMarkdown(t *testing.T) {
+9 -23
View File
@@ -14,9 +14,8 @@ import (
"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"
"gopkg.in/yaml.v2"
)
// CouldBe indicates a file with the filename could be a template,
@@ -96,27 +95,14 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
}{}
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 = filepath.Base(it.FileName)
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
}
templateBody, err := markdown.ExtractMetadata(string(content), it)
if err != nil {
return nil, err
}
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 {
+22 -26
View File
@@ -9,86 +9,82 @@ import (
"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 {
func validateMetadata(it structs.IssueTemplate) bool {
/*
A legacy to keep the unit tests working.
Copied from the method "func (it IssueTemplate) Valid() bool", the original method has been removed.
Because it becomes quite complicated to validate an issue template which is support yaml form now.
The new way to validate an issue template is to call the Validate in modules/issue/template,
*/
return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
}
func TestExtractMetadata(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta IssueTemplate
var meta structs.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)
assert.Equal(t, metaTest, meta)
assert.True(t, meta.Valid())
assert.True(t, validateMetadata(meta))
})
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate
var meta structs.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 IssueTemplate
var meta structs.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 IssueTemplate
var meta structs.IssueTemplate
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
assert.NoError(t, err)
assert.Equal(t, "", body)
assert.Equal(t, metaTest, meta)
assert.True(t, meta.Valid())
assert.True(t, validateMetadata(meta))
})
}
func TestExtractMetadataBytes(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta IssueTemplate
var meta structs.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())
assert.True(t, validateMetadata(meta))
})
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate
var meta structs.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
var meta structs.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
var meta structs.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())
assert.True(t, validateMetadata(meta))
})
}
@@ -101,7 +97,7 @@ labels:
- bug
- "test label"`
bodyTest = "This is the body"
metaTest = IssueTemplate{
metaTest = structs.IssueTemplate{
Name: "Test",
About: "A Test",
Title: "Test Title",
+1 -30
View File
@@ -22,7 +22,6 @@ import (
"time"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/user"
@@ -963,11 +962,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
if InstallLock && InternalToken == "" {
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
// some users do cluster deployment, they still depend on this auto-generating behavior.
generateSaveInternalToken()
}
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")
if len(cfgdata) == 0 {
@@ -1156,8 +1150,6 @@ func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {
return authorizedPrincipalsAllow, true
}
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
// If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear.
func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
// don't allow setting both URI and verbatim string
uri := sec.Key(uriKey).String()
@@ -1181,15 +1173,7 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
if err != nil {
log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err)
}
val := strings.TrimSpace(string(buf))
if val == "" {
// The file shouldn't be empty, otherwise we can not know whether the user has ever set the KEY or KEY_URI
// For example: if INTERNAL_TOKEN_URI=file:///empty-file,
// Then if the token is re-generated during installation and saved to INTERNAL_TOKEN
// Then INTERNAL_TOKEN and INTERNAL_TOKEN_URI both exist, that's a fatal error (they shouldn't)
log.Fatal("Failed to read %s (%s): the file is empty", uriKey, tempURI.RequestURI())
}
return val
return strings.TrimSpace(string(buf))
// only file URIs are allowed
default:
@@ -1198,19 +1182,6 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
}
}
// generateSaveInternalToken generates and saves the internal token to app.ini
func generateSaveInternalToken() {
token, err := generate.NewInternalToken()
if err != nil {
log.Fatal("Error generate internal token: %v", err)
}
InternalToken = token
CreateOrAppendToCustomConf("security.INTERNAL_TOKEN", func(cfg *ini.File) {
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
})
}
// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
+13 -50
View File
@@ -5,12 +5,8 @@
package structs
import (
"fmt"
"path"
"strings"
"path/filepath"
"time"
"gopkg.in/yaml.v3"
)
// StateType issue state type
@@ -147,47 +143,14 @@ type IssueFormField struct {
// IssueTemplate represents an issue template for a repository
// swagger:model
type IssueTemplate struct {
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
Labels IssueTemplateLabels `json:"labels" yaml:"labels"`
Ref string `json:"ref" yaml:"ref"`
Content string `json:"content" yaml:"-"`
Fields []*IssueFormField `json:"body" yaml:"body"`
FileName string `json:"file_name" yaml:"-"`
}
type IssueTemplateLabels []string
func (l *IssueTemplateLabels) UnmarshalYAML(value *yaml.Node) error {
var labels []string
if value.IsZero() {
*l = labels
return nil
}
switch value.Kind {
case yaml.ScalarNode:
str := ""
err := value.Decode(&str)
if err != nil {
return err
}
for _, v := range strings.Split(str, ",") {
if v = strings.TrimSpace(v); v == "" {
continue
}
labels = append(labels, v)
}
*l = labels
return nil
case yaml.SequenceNode:
if err := value.Decode(&labels); err != nil {
return err
}
*l = labels
return nil
}
return fmt.Errorf("line %d: cannot unmarshal %s into IssueTemplateLabels", value.Line, value.ShortTag())
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
Labels []string `json:"labels" yaml:"labels"`
Ref string `json:"ref" yaml:"ref"`
Content string `json:"content" yaml:"-"`
Fields []*IssueFormField `json:"body" yaml:"body"`
FileName string `json:"file_name" yaml:"-"`
}
// IssueTemplateType defines issue template type
@@ -200,14 +163,14 @@ const (
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
func (it IssueTemplate) Type() IssueTemplateType {
if base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" {
if it.Name == "config.yaml" || it.Name == "config.yml" {
// ignore config.yaml which is a special configuration file
return ""
}
if ext := path.Ext(it.FileName); ext == ".md" {
if ext := filepath.Ext(it.FileName); ext == ".md" {
return IssueTemplateTypeMarkdown
} else if ext == ".yaml" || ext == ".yml" {
return IssueTemplateTypeYaml
return "yaml"
}
return ""
return IssueTemplateTypeYaml
}
-106
View File
@@ -1,106 +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 structs
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestIssueTemplate_Type(t *testing.T) {
tests := []struct {
fileName string
want IssueTemplateType
}{
{
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.yaml",
want: IssueTemplateTypeYaml,
},
{
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.md",
want: IssueTemplateTypeMarkdown,
},
{
fileName: ".gitea/ISSUE_TEMPLATE/bug_report.txt",
want: "",
},
{
fileName: ".gitea/ISSUE_TEMPLATE/config.yaml",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.fileName, func(t *testing.T) {
it := IssueTemplate{
FileName: tt.fileName,
}
assert.Equal(t, tt.want, it.Type())
})
}
}
func TestIssueTemplateLabels_UnmarshalYAML(t *testing.T) {
tests := []struct {
name string
content string
tmpl *IssueTemplate
want *IssueTemplate
wantErr string
}{
{
name: "array",
content: `labels: ["a", "b", "c"]`,
tmpl: &IssueTemplate{
Labels: []string{"should_be_overwrote"},
},
want: &IssueTemplate{
Labels: []string{"a", "b", "c"},
},
},
{
name: "string",
content: `labels: "a,b,c"`,
tmpl: &IssueTemplate{
Labels: []string{"should_be_overwrote"},
},
want: &IssueTemplate{
Labels: []string{"a", "b", "c"},
},
},
{
name: "empty",
content: `labels:`,
tmpl: &IssueTemplate{
Labels: []string{"should_be_overwrote"},
},
want: &IssueTemplate{
Labels: nil,
},
},
{
name: "error",
content: `
labels:
a: aa
b: bb
`,
tmpl: &IssueTemplate{},
wantErr: "line 3: cannot unmarshal !!map into IssueTemplateLabels",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := yaml.Unmarshal([]byte(tt.content), tt.tmpl)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, tt.tmpl)
}
})
}
}
+1 -2
View File
@@ -6,8 +6,7 @@ package system
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
type RuntimeState struct {
LastAppPath string `json:"last_app_path"`
LastCustomConf string `json:"last_custom_conf"`
LastAppPath string `json:"last_app_path"`
}
// Name returns the item name
+46
View File
@@ -0,0 +1,46 @@
// 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 (
"strconv"
"code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/cache"
)
func genKey(key string) string {
return "system.setting." + key
}
// GetSetting returns the setting value via the key
func GetSetting(key string) (string, error) {
return cache.GetString(genKey(key), func() (string, error) {
res, err := system.GetSetting(key)
if err != nil {
return "", err
}
return res.SettingValue, 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)
b, _ := strconv.ParseBool(s)
return b
}
// SetSetting sets the setting value
func SetSetting(key, value string, version int) error {
cache.Remove(genKey(key))
return system.SetSetting(&system.Setting{
SettingKey: key,
SettingValue: value,
Version: version,
})
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package system
import (
"fmt"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
)
func genUserKey(userID int64, key string) string {
return fmt.Sprintf("user_%d.setting.%s", userID, key)
}
// GetUserSetting returns the user setting value via the key
func GetUserSetting(userID int64, key string) (string, error) {
return cache.GetString(genUserKey(userID, key), func() (string, error) {
res, err := user.GetSetting(userID, key)
if err != nil {
return "", err
}
return res.SettingValue, nil
})
}
// SetUserSetting sets the user setting value
func SetUserSetting(userID int64, key, value string) error {
cache.Remove(genUserKey(userID, key))
return user.SetUserSetting(userID, key, value)
}
+5 -4
View File
@@ -42,6 +42,7 @@ import (
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
system_module "code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
@@ -86,7 +87,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion
},
"DisableGravatar": func() bool {
return system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
},
"DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName
@@ -646,7 +647,7 @@ func SVG(icon string, others ...interface{}) template.HTML {
// Avatar renders user avatars. args: user, size (int), class (string)
func Avatar(item interface{}, others ...interface{}) template.HTML {
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar vm", others...)
switch t := item.(type) {
case *user_model.User:
@@ -677,7 +678,7 @@ func AvatarByAction(action *activities_model.Action, others ...interface{}) temp
// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
src := repo.RelAvatarLink()
if src != "" {
@@ -688,7 +689,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
if src != "" {
+1 -1
View File
@@ -103,5 +103,5 @@ func (ts TimeStamp) FormatDate() string {
// IsZero is zero time
func (ts TimeStamp) IsZero() bool {
return int64(ts) == 0
return ts.AsTimeInLocation(time.Local).IsZero()
}
-3
View File
@@ -742,9 +742,6 @@ create_oauth2_application_button=Vytvořit aplikaci
create_oauth2_application_success=Úspěšně jste vytvořili novou OAuth2 aplikaci.
update_oauth2_application_success=Úspěšně jste aktualizovali OAuth2 aplikaci.
oauth2_application_name=Název aplikace
oauth2_select_type=Jaký typ aplikace se hodí?
oauth2_type_web=Webová (např. Node.JS, Tomcat, Go)
oauth2_type_native=Nativní (např. Mobil, Desktop, Prohlížeč)
oauth2_redirect_uri=URI přesměrování
save_application=Uložit
oauth2_client_id=ID klienta
-3
View File
@@ -736,9 +736,6 @@ create_oauth2_application_button=Anwendung erstellen
create_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung erstellt.
update_oauth2_application_success=Du hast erfolgreich eine neue OAuth2 Anwendung bearbeitet.
oauth2_application_name=Name der Anwendung
oauth2_select_type=Welcher Anwendungstyp passt?
oauth2_type_web=Web (z.B. Node.JS, Tomcat, Go)
oauth2_type_native=Native (z.B. Mobile, Desktop, Browser)
oauth2_redirect_uri=Weiterleitungs-URI
save_application=Speichern
oauth2_client_id=Client-ID
-3
View File
@@ -738,9 +738,6 @@ create_oauth2_application_button=Δημιουργία Εφαρμογής
create_oauth2_application_success=Δημιουργήσατε επιτυχώς μια νέα εφαρμογή OAuth2.
update_oauth2_application_success=Ενημερώσατε επιτυχώς την εφαρμογή OAuth2.
oauth2_application_name=Όνομα Εφαρμογής
oauth2_select_type=Ποιος τύπος εφαρμογής ταιριάζει;
oauth2_type_web=Web (πχ Node.JS, Tomcat, Go)
oauth2_type_native=Εγγενές (π.χ. Κινητό, Επιφάνεια Εργασίας, Πρόγραμμα Περιήγησης)
oauth2_redirect_uri=URI Ανακατεύθυνσης
save_application=Αποθήκευση
oauth2_client_id=Ταυτότητα Πελάτη
-3
View File
@@ -749,9 +749,6 @@ create_oauth2_application_button=Crear Aplicación
create_oauth2_application_success=Ha creado una nueva aplicación OAuth2 con éxito.
update_oauth2_application_success=Ha actualizado correctamente la aplicación OAuth2.
oauth2_application_name=Nombre de la Aplicación
oauth2_select_type=¿Qué tipo de aplicación es?
oauth2_type_web=Web (por ejemplo: Node.JS, Tomcat, Go)
oauth2_type_native=Nativa (por ejemplo, móvil, escritorio, navegador)
oauth2_redirect_uri=URI de redireccionado
save_application=Guardar
oauth2_client_id=ID de cliente
-3
View File
@@ -675,9 +675,6 @@ create_oauth2_application_button=ایجاد برنامه
create_oauth2_application_success=برنامه OAuth2 جدید شما با موفقیت ساخته شد.
update_oauth2_application_success=برنامه OAuth2 با موفقیت به‎روزرسانی شد.
oauth2_application_name=نام برنامه
oauth2_select_type=کدام نوع برنامه متناسب است؟
oauth2_type_web=وب (مثلا Node.JS, Tomcat, Go)
oauth2_type_native=بومی (مثلا، Mobile, Desktop, Browser)
oauth2_redirect_uri=تغییر مسیر به نشانی اینترنتی
save_application=ذخيره
oauth2_client_id=شناسه کلاینت
-3
View File
@@ -742,9 +742,6 @@ create_oauth2_application_button=Créer une application
create_oauth2_application_success=Vous avez créé avec succès une nouvelle application OAuth2.
update_oauth2_application_success=Vous avez mis à jour l'application OAuth2 avec succès.
oauth2_application_name=Nom de l'Application
oauth2_select_type=De quel type d'application s'agit-il ?
oauth2_type_web=Web (par exemple Node.JS, Tomcat, Go)
oauth2_type_native=Native (par exemple Mobile, Bureau, Navigateur)
oauth2_redirect_uri=URL de redirection
save_application=Enregistrer
oauth2_client_id=ID du client
-3
View File
@@ -526,9 +526,6 @@ create_oauth2_application_button=Alkalmazás létrehozása
create_oauth2_application_success=Sikerült létrehozni egy új OAuth2 alkalmazást.
update_oauth2_application_success=Sikerült módosítani az OAuth2 alkalmazást.
oauth2_application_name=Alkalmazásnév
oauth2_select_type=Melyik típus felelne meg?
oauth2_type_web=Web (pl. Node.JS, Tomcat, Go)
oauth2_type_native=Natív (pl. Mobil, PC, Böngésző)
oauth2_redirect_uri=Átirányítási URI
save_application=Mentés
oauth2_client_id=Ügyfélazonosító
-3
View File
@@ -519,9 +519,6 @@ create_oauth2_application_button=Buat Aplikasi
create_oauth2_application_success=Anda berhasil membuat aplikasi OAuth2 baru.
update_oauth2_application_success=Anda berhasil memperbarui aplikasi OAuth2.
oauth2_application_name=Nama Aplikasi
oauth2_select_type=Aplikasi tipe apa yang cocok?
oauth2_type_web=Web (contohnya Node.JS, Tomcat, Go)
oauth2_type_native=Asli (contohnya perangkat genggaman, desktop, peramban)
oauth2_redirect_uri=URI Pengalihan
save_application=Simpan
oauth2_client_id=ID Klien
-3
View File
@@ -553,9 +553,6 @@ access_token_deletion=Eyða Aðgangslykli
create_oauth2_application_button=Skapa Forrit
update_oauth2_application_success=Þú hefur uppfært OAuth2 forritið.
oauth2_application_name=Forritsheiti
oauth2_select_type=Hvaða forritsgerð passar?
oauth2_type_web=Net (t.d. Node.JS, Tomcat, Go)
oauth2_type_native=Á kerfi (t.d. síma, tölvu, vafra)
oauth2_redirect_uri=Áframsendingar Vefslóð
save_application=Vista
oauth2_client_id=Auðkenni Notanda
-3
View File
@@ -736,9 +736,6 @@ create_oauth2_application_button=Crea applicazione
create_oauth2_application_success=Hai creato con successo una nuova applicazione OAuth2.
update_oauth2_application_success=Hai aggiornato con successo l'applicazione OAuth2.
oauth2_application_name=Nome applicazione
oauth2_select_type=Quale tipo di applicazione é adatto?
oauth2_type_web=Web (es: Node.JS, Tomcat, Go)
oauth2_type_native=Nativo (es. Mobile, Desktop, Browser)
oauth2_redirect_uri=URI di reindirizzamento
save_application=Salva
oauth2_client_id=Client ID
-3
View File
@@ -742,9 +742,6 @@ create_oauth2_application_button=アプリケーション作成
create_oauth2_application_success=新しいOAuth2アプリケーションを作成しました。
update_oauth2_application_success=OAuth2アプリケーションを更新しました。
oauth2_application_name=アプリケーション名
oauth2_select_type=アプリケーションタイプを選択
oauth2_type_web=Web (Node.JS, Tomcat, Go など)
oauth2_type_native=Native (モバイル, デスクトップ, ブラウザーなど)
oauth2_redirect_uri=リダイレクトURI
save_application=保存
oauth2_client_id=クライアントID
-3
View File
@@ -736,9 +736,6 @@ create_oauth2_application_button=Izveidot lietotni
create_oauth2_application_success=OAuth2 lietotne veiksmīgi izveidota.
update_oauth2_application_success=OAuth2 lietotne veiksmīgi atjaunināta.
oauth2_application_name=Lietotnes nosaukums
oauth2_select_type=Kāds lietotnes veids visvairāk atbilst?
oauth2_type_web=Tīmekļa (piemēram, Node.JS, Tomcat, Go)
oauth2_type_native=Specializētā (piemēram, mobilā, darbvirsmas, tīmekļa pārlūks)
oauth2_redirect_uri=Novirzīšanas URI
save_application=Saglabāt
oauth2_client_id=Klienta ID
-3
View File
@@ -489,9 +489,6 @@ create_oauth2_application_button=അപ്ലിക്കേഷൻ സൃഷ്
create_oauth2_application_success=നിങ്ങൾ വിജയകരമായി ഒരു പുതിയ OAuth2 അപ്ലിക്കേഷൻ സൃഷ്ടിച്ചു.
update_oauth2_application_success=നിങ്ങൾ വിജയകരമായി ഒരു പുതിയ OAuth2 അപ്ലിക്കേഷൻ പുതുക്കി.
oauth2_application_name=അപ്ലിക്കേഷന്റെ പേര്
oauth2_select_type=ഏത് തരം അപ്ലിക്കേഷനാണ് ഇതു്?
oauth2_type_web=വെബ് (e.g. Node.JS, Tomcat, Go)
oauth2_type_native=നേറ്റീവ് (ഉദാ. മൊബൈൽ, ഡെസ്ക്ടോപ്പ്, ബ്രൌസർ)
oauth2_redirect_uri=URI റീഡയറക്‌ട് ചെയ്യുക
save_application=സംരക്ഷിയ്ക്കുക
oauth2_client_id=ക്ലൈന്റ് ഐഡി
-3
View File
@@ -736,9 +736,6 @@ create_oauth2_application_button=Maak applicatie
create_oauth2_application_success=Je hebt met succes een nieuwe OAuth2-applicatie aangemaakt.
update_oauth2_application_success=Je hebt de OAuth2-applicatie succesvol bijgewerkt.
oauth2_application_name=Applicatie naam
oauth2_select_type=Welk type toepassing past?
oauth2_type_web=Web (bijv. Node.JS, Tomcat, Go)
oauth2_type_native=Native (bijv. Mobiel, Desktop, Browser)
oauth2_redirect_uri=Omleidings URL
save_application=Opslaan
oauth2_client_id=Client-ID
-3
View File
@@ -688,9 +688,6 @@ create_oauth2_application_button=Stwórz aplikację
create_oauth2_application_success=Udało Ci się stworzyć nową aplikację OAuth2.
update_oauth2_application_success=Udało Ci się zaktualizować aplikację OAuth2.
oauth2_application_name=Nazwa aplikacji
oauth2_select_type=Który typ aplikacji jest dla niej właściwy?
oauth2_type_web=Webowa (np. Node.JS, Tomcat, Go)
oauth2_type_native=Natywna (np. mobilna, pulpitowa, przeglądarkowa)
oauth2_redirect_uri=URI przekierowania
save_application=Zapisz
oauth2_client_id=ID klienta
-3
View File
@@ -738,9 +738,6 @@ create_oauth2_application_button=Criar aplicativo
create_oauth2_application_success=Você criou com sucesso um novo aplicativo OAuth2.
update_oauth2_application_success=Você alterou com sucesso o aplicativo OAuth2.
oauth2_application_name=Nome do aplicativo
oauth2_select_type=Que tipo de aplicativo se encaixa?
oauth2_type_web=Web (exemplo: Node.JS, Tomcat, Go)
oauth2_type_native=Nativo (exemplo: Celular, Computador, Navegador)
oauth2_redirect_uri=Redirecionar URI
save_application=Salvar
oauth2_client_id=Client ID
+1 -3
View File
@@ -749,9 +749,7 @@ create_oauth2_application_button=Criar aplicação
create_oauth2_application_success=Criou com sucesso uma nova aplicação OAuth2.
update_oauth2_application_success=Modificou com sucesso a aplicação OAuth2.
oauth2_application_name=Nome da aplicação
oauth2_select_type=Que tipo de aplicaçãoo se encaixa?
oauth2_type_web=Web (por exemplo: Node.JS, Tomcat, Go)
oauth2_type_native=Nativa (por exemplo: Telemóvel, Computador, Navegador)
oauth2_confidential_client=Cliente confidencial. Escolha para aplicações que mantêm o segredo confidencial, tais como aplicações web. Não escolha para aplicações nativas, incluindo aplicações para computador e aplicações móveis.
oauth2_redirect_uri=URI de reencaminhamento
save_application=Guardar
oauth2_client_id=ID do cliente
-3
View File
@@ -725,9 +725,6 @@ create_oauth2_application_button=Создать приложение
create_oauth2_application_success=Вы успешно создали новое приложение OAuth2.
update_oauth2_application_success=Изменения настроек приложения OAuth2 успешно применены.
oauth2_application_name=Имя приложения
oauth2_select_type=Какой тип приложения подходит?
oauth2_type_web=Веб (например: Node.JS, Tomcat, Go)
oauth2_type_native=Нативный (например: телефон, ПК, браузер)
oauth2_redirect_uri=URI переадресации
save_application=Сохранить
oauth2_client_id=ID клиента
-3
View File
@@ -657,9 +657,6 @@ create_oauth2_application_button=යෙදුම සාදන්න
create_oauth2_application_success=ඔබ නව Oauth2 යෙදුමක් සාර්ථකව නිර්මාණය කර ඇත.
update_oauth2_application_success=ඔබ Oauth2 යෙදුම සාර්ථකව යාවත්කාලීන කර ඇත.
oauth2_application_name=යෙදුමේ නම
oauth2_select_type=කුමන යෙදුම් වර්ගය ගැලපේද?
oauth2_type_web=වෙබ් (උදා: Node.JS, ටොම්කැට්, යන්න)
oauth2_type_native=දේශීය (උදා: ජංගම, පරිගණක, බ්රව්සරය)
oauth2_redirect_uri=නැවත හරවා යැවීමේ URI
save_application=සුරකින්න
oauth2_client_id=අනුග්‍රාහකයේ හැඳු.
-1
View File
@@ -738,7 +738,6 @@ create_oauth2_application_button=Vytvoriť aplikáciu
create_oauth2_application_success=Úspešne ste vytvorili novú aplikáciu OAuth2.
update_oauth2_application_success=Úspešne ste aktualizovali aplikáciu OAuth2.
oauth2_application_name=Názov aplikácie
oauth2_select_type=Ktorý typ aplikácie sa hodí?
oauth2_redirect_uri=Presmerovanie URI
save_application=Uložiť
oauth2_client_id=ID klienta
-3
View File
@@ -561,9 +561,6 @@ create_oauth2_application_button=Skapa applikation
create_oauth2_application_success=Du har lyckats med att skapa en ny OAuth2-applikation.
update_oauth2_application_success=Du har lyckats uppdatera OAuth2-applikationen.
oauth2_application_name=Applikationsnamn
oauth2_select_type=Vilken typ av applikation passar?
oauth2_type_web=Webb (t.ex. Node.JS, Tomcat, Go)
oauth2_type_native=Native (ex. Mobil, Desktop, Webbläsare)
oauth2_redirect_uri=Omdirigerings-URI
save_application=Spara
oauth2_client_id=Klient-ID
-3
View File
@@ -742,9 +742,6 @@ create_oauth2_application_button=Uygulama Oluştur
create_oauth2_application_success=Başarıyla yeni bir OAuth2 uygulaması oluşturdunuz.
update_oauth2_application_success=OAuth2 uygulamasını başarıyla güncellediniz.
oauth2_application_name=Uygulama Adı
oauth2_select_type=Hangi uygulama türü uyuyor?
oauth2_type_web=Web (ör. Node.JS, Tomcat, Go)
oauth2_type_native=Yerel (ör. Mobil, Masaüstü, Tarayıcı)
oauth2_redirect_uri=Yönlendirme URI'si
save_application=Kaydet
oauth2_client_id=İstemci Kimliği
-3
View File
@@ -683,9 +683,6 @@ create_oauth2_application_button=Створити програму
create_oauth2_application_success=Ви успішно створили нову програму OAuth2.
update_oauth2_application_success=Ви успішно оновили програму OAuth2.
oauth2_application_name=Назва програми
oauth2_select_type=Який тип програми підходить?
oauth2_type_web=Веб (напр. Node.JS, Tomcat, Go)
oauth2_type_native=Рідний (напр. мобільний, робочий стіл, веб-переглядач)
oauth2_redirect_uri=URI перенаправлення
save_application=Зберегти
oauth2_client_id=ID Клієнта
-3
View File
@@ -749,9 +749,6 @@ create_oauth2_application_button=创建应用
create_oauth2_application_success=您已成功创建了一个新的 OAuth2 应用。
update_oauth2_application_success=您已成功更新了此 OAuth2 应用。
oauth2_application_name=应用名称
oauth2_select_type=哪种应用类型合适?
oauth2_type_web=Web (例如 Node.JS, Tomacat, Go)
oauth2_type_native=原生 (例如移动,桌面,浏览器)
oauth2_redirect_uri=重定向 URI
save_application=保存
oauth2_client_id=客户端ID
-3
View File
@@ -740,9 +740,6 @@ create_oauth2_application_button=建立應用程式
create_oauth2_application_success=您已成功新增一個 OAuth2 應用程式。
update_oauth2_application_success=您已成功更新了 OAuth2 應用程式。
oauth2_application_name=應用程式名稱
oauth2_select_type=適用哪種程式類別?
oauth2_type_web=Web (例如 Node.JS, Tomacat, Go)
oauth2_type_native=原生應用程式 (Mobile, Desktop, Browser)
oauth2_redirect_uri=重新導向 URI
save_application=儲存
oauth2_client_id=客戶端 ID
+6 -17
View File
@@ -21,19 +21,12 @@ import (
packages_service "code.gitea.io/gitea/services/packages"
)
// https://peps.python.org/pep-0426/#name
// https://www.python.org/dev/peps/pep-0503/#normalized-names
var normalizer = strings.NewReplacer(".", "-", "_", "-")
var nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\z`)
var nameMatcher = regexp.MustCompile(`\A[a-zA-Z0-9\.\-_]+\z`)
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
var versionMatcher = regexp.MustCompile(`\Av?` +
`(?:[0-9]+!)?` + // epoch
`[0-9]+(?:\.[0-9]+)*` + // release segment
`(?:[-_\.]?(?:a|b|c|rc|alpha|beta|pre|preview)[-_\.]?[0-9]*)?` + // pre-release
`(?:-[0-9]+|[-_\.]?(?:post|rev|r)[-_\.]?[0-9]*)?` + // post release
`(?:[-_\.]?dev[-_\.]?[0-9]*)?` + // dev release
`(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?` + // local version
`\z`)
// https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
var versionMatcher = regexp.MustCompile(`^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$`)
func apiError(ctx *context.Context, status int, obj interface{}) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
@@ -128,7 +121,7 @@ func UploadPackageFile(ctx *context.Context) {
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
packageVersion := ctx.Req.FormValue("version")
if !isValidNameAndVersion(packageName, packageVersion) {
if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
apiError(ctx, http.StatusBadRequest, "invalid name or version")
return
}
@@ -146,7 +139,7 @@ func UploadPackageFile(ctx *context.Context) {
Name: packageName,
Version: packageVersion,
},
SemverCompatible: false,
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: &pypi_module.Metadata{
Author: ctx.Req.FormValue("author"),
@@ -177,7 +170,3 @@ func UploadPackageFile(ctx *context.Context) {
ctx.Status(http.StatusCreated)
}
func isValidNameAndVersion(packageName, packageVersion string) bool {
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
}
-39
View File
@@ -1,39 +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 pypi
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsValidNameAndVersion(t *testing.T) {
// The test cases below were created from the following Python PEPs:
// https://peps.python.org/pep-0426/#name
// https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
// Valid Cases
assert.True(t, isValidNameAndVersion("A", "1.0.1"))
assert.True(t, isValidNameAndVersion("Test.Name.1234", "1.0.1"))
assert.True(t, isValidNameAndVersion("test_name", "1.0.1"))
assert.True(t, isValidNameAndVersion("test-name", "1.0.1"))
assert.True(t, isValidNameAndVersion("test-name", "v1.0.1"))
assert.True(t, isValidNameAndVersion("test-name", "2012.4"))
assert.True(t, isValidNameAndVersion("test-name", "1.0.1-alpha"))
assert.True(t, isValidNameAndVersion("test-name", "1.0.1a1"))
assert.True(t, isValidNameAndVersion("test-name", "1.0b2.r345.dev456"))
assert.True(t, isValidNameAndVersion("test-name", "1!1.0.1"))
assert.True(t, isValidNameAndVersion("test-name", "1.0.1+local.1"))
// Invalid Cases
assert.False(t, isValidNameAndVersion(".test-name", "1.0.1"))
assert.False(t, isValidNameAndVersion("test!name", "1.0.1"))
assert.False(t, isValidNameAndVersion("-test-name", "1.0.1"))
assert.False(t, isValidNameAndVersion("test-name-", "1.0.1"))
assert.False(t, isValidNameAndVersion("test-name", "a1.0.1"))
assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa"))
assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta"))
}
+2 -6
View File
@@ -77,9 +77,7 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo
})
}
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: filename + ".gz",
})
ctx.SetServeHeaders(filename + ".gz")
zw := gzip.NewWriter(ctx.Resp)
defer zw.Close()
@@ -117,9 +115,7 @@ func ServePackageSpecification(ctx *context.Context) {
return
}
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: filename,
})
ctx.SetServeHeaders(filename)
zw := zlib.NewWriter(ctx.Resp)
defer zw.Close()
+1 -1
View File
@@ -898,7 +898,7 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Group("/{index}", func() {
m.Combo("").Get(repo.GetIssue).
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue).
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
Delete(reqToken(), reqAdmin(), repo.DeleteIssue)
m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
+1 -5
View File
@@ -1443,11 +1443,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
end = totalNumberOfFiles
}
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*api.ChangedFile, 0, lenFiles)
apiFiles := make([]*api.ChangedFile, 0, end-start)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
}
+26 -15
View File
@@ -7,6 +7,7 @@ package common
import (
"fmt"
"io"
"net/url"
"path"
"path/filepath"
"strings"
@@ -52,44 +53,50 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
buf = buf[:n]
}
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute)
if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}
opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}
fileName := path.Base(filePath)
sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")
mimeType := ""
charset := ""
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(filePath))
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
fileExtension := strings.ToLower(filepath.Ext(fileName))
mimeType = setting.MimeTypeMap.Map[fileExtension]
}
if opts.ContentType == "" {
if mimeType == "" {
if sniffedType.IsBrowsableBinaryType() {
opts.ContentType = sniffedType.GetMimeType()
mimeType = sniffedType.GetMimeType()
} else if isPlain {
opts.ContentType = "text/plain"
mimeType = "text/plain"
} else {
opts.ContentType = typesniffer.ApplicationOctetStream
mimeType = typesniffer.ApplicationOctetStream
}
}
if isPlain {
var charset string
charset, err = charsetModule.DetectEncoding(buf)
if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err)
charset = "utf-8"
}
opts.ContentTypeCharset = strings.ToLower(charset)
}
if charset != "" {
ctx.Resp.Header().Set("Content-Type", mimeType+"; charset="+strings.ToLower(charset))
} else {
ctx.Resp.Header().Set("Content-Type", mimeType)
}
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
isSVG := sniffedType.IsSvgImage()
// serve types that can present a security risk with CSP
@@ -102,12 +109,16 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
ctx.Resp.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
}
opts.Disposition = "inline"
disposition := "inline"
if isSVG && !setting.UI.SVG.Enabled {
opts.Disposition = "attachment"
disposition = "attachment"
}
ctx.SetServeHeaders(opts)
// encode filename per https://datatracker.ietf.org/doc/html/rfc5987
encodedFileName := `filename*=UTF-8''` + url.PathEscape(fileName)
ctx.Resp.Header().Set("Content-Disposition", disposition+"; "+encodedFileName)
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
_, err = ctx.Resp.Write(buf)
if err != nil {
+3 -13
View File
@@ -76,31 +76,21 @@ func InitGitServices() {
mustInit(repo_service.Init)
}
func syncAppConfForGit(ctx context.Context) error {
func syncAppPathForGit(ctx context.Context) error {
runtimeState := new(system.RuntimeState)
if err := system.AppState.Get(runtimeState); err != nil {
return err
}
updated := false
if runtimeState.LastAppPath != setting.AppPath {
log.Info("AppPath changed from '%s' to '%s'", runtimeState.LastAppPath, setting.AppPath)
runtimeState.LastAppPath = setting.AppPath
updated = true
}
if runtimeState.LastCustomConf != setting.CustomConf {
log.Info("CustomConf changed from '%s' to '%s'", runtimeState.LastCustomConf, setting.CustomConf)
runtimeState.LastCustomConf = setting.CustomConf
updated = true
}
if updated {
log.Info("re-sync repository hooks ...")
mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
log.Info("re-write ssh public keys ...")
mustInit(asymkey_model.RewriteAllPublicKeys)
runtimeState.LastAppPath = setting.AppPath
return system.AppState.Set(runtimeState)
}
return nil
@@ -163,7 +153,7 @@ func GlobalInitInstalled(ctx context.Context) {
mustInit(repo_migrations.Init)
eventsource.GetManager().Init()
mustInitCtx(ctx, syncAppConfForGit)
mustInitCtx(ctx, syncAppPathForGit)
mustInit(ssh.Init)
+5 -9
View File
@@ -473,16 +473,12 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
// the internal token could be read from INTERNAL_TOKEN or INTERNAL_TOKEN_URI (the file is guaranteed to be non-empty)
// if there is no InternalToken, generate one and save to security.INTERNAL_TOKEN
if setting.InternalToken == "" {
var internalToken string
if internalToken, err = generate.NewInternalToken(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
return
}
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
var internalToken string
if internalToken, err = generate.NewInternalToken(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
return
}
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
if setting.SecretKey == "" {
+1 -1
View File
@@ -159,7 +159,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
return &smtp.Source{
Auth: form.SMTPAuth,
Host: form.SMTPHost,
Addr: form.SMTPAddr,
Port: form.SMTPPort,
AllowedDomains: form.AllowedDomains,
ForceSMTPS: form.ForceSMTPS,
+2 -5
View File
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
system_module "code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/mailer"
@@ -202,11 +203,7 @@ func ChangeConfig(ctx *context.Context) {
value := ctx.FormString("value")
version := ctx.FormInt("version")
if err := system_model.SetSetting(&system_model.Setting{
SettingKey: key,
SettingValue: value,
Version: version,
}); err != nil {
if err := system_module.SetSetting(key, value, version); err != nil {
log.Error("set setting failed: %v", err)
ctx.JSON(http.StatusOK, map[string]string{
"err": ctx.Tr("admin.config.set_setting_failed", key),
-7
View File
@@ -783,13 +783,6 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
return
}
// Register last login
user.SetLastLogin()
if err := user_model.UpdateUserCols(ctx, user, "last_login_unix"); err != nil {
ctx.ServerError("UpdateUserCols", err)
return
}
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
ctx.Redirect(setting.AppSubURL + "/")
}
+2
View File
@@ -5,6 +5,7 @@
package feed
import (
"net/http"
"time"
activities_model "code.gitea.io/gitea/models/activities"
@@ -58,6 +59,7 @@ func showUserFeed(ctx *context.Context, formatType string) {
// writeFeed write a feeds.Feed as atom or rss to ctx.Resp
func writeFeed(ctx *context.Context, feed *feeds.Feed, formatType string) {
ctx.Resp.WriteHeader(http.StatusOK)
if formatType == "atom" {
ctx.Resp.Header().Set("Content-Type", "application/atom+xml;charset=utf-8")
if err := feed.WriteAtom(ctx.Resp); err != nil {
+1 -4
View File
@@ -597,10 +597,7 @@ func RegisterRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/favicon.ico", func(ctx *context.Context) {
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: "favicon.png",
})
http.ServeFile(ctx.Resp, ctx.Req, path.Join(setting.StaticRootPath, "public/img/favicon.png"))
ctx.ServeFile(path.Join(setting.StaticRootPath, "public/img/favicon.png"))
})
m.Group("/{username}", func() {
m.Get(".png", func(ctx *context.Context) { ctx.Error(http.StatusNotFound) })
+3 -3
View File
@@ -58,10 +58,10 @@ var ErrUnsupportedLoginType = errors.New("Login source is unknown")
func Authenticate(a smtp.Auth, source *Source) error {
tlsConfig := &tls.Config{
InsecureSkipVerify: source.SkipVerify,
ServerName: source.Host,
ServerName: source.Addr,
}
conn, err := net.Dial("tcp", net.JoinHostPort(source.Host, strconv.Itoa(source.Port)))
conn, err := net.Dial("tcp", net.JoinHostPort(source.Addr, strconv.Itoa(source.Port)))
if err != nil {
return err
}
@@ -71,7 +71,7 @@ func Authenticate(a smtp.Auth, source *Source) error {
conn = tls.Client(conn, tlsConfig)
}
client, err := smtp.NewClient(conn, source.Host)
client, err := smtp.NewClient(conn, source.Addr)
if err != nil {
return fmt.Errorf("failed to create NewClient: %w", err)
}
+1 -1
View File
@@ -19,7 +19,7 @@ import (
// Source holds configuration for the SMTP login source.
type Source struct {
Auth string
Host string
Addr string
Port int
AllowedDomains string `xorm:"TEXT"`
ForceSMTPS bool
@@ -32,7 +32,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str
var auth smtp.Auth
switch source.Auth {
case PlainAuthentication:
auth = smtp.PlainAuth("", userName, password, source.Host)
auth = smtp.PlainAuth("", userName, password, source.Addr)
case LoginAuthentication:
auth = &loginAuthenticator{userName, password}
case CRAMMD5Authentication:
+1 -1
View File
@@ -45,7 +45,7 @@ type AuthenticationForm struct {
IsActive bool
IsSyncEnabled bool
SMTPAuth string
SMTPHost string
SMTPAddr string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`
+5 -6
View File
@@ -8,7 +8,6 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
@@ -219,21 +218,21 @@ func DeleteUnadoptedRepository(doer, u *user_model.User, repoName string) error
return util.RemoveAll(repoPath)
}
type unadoptedRepositories struct {
type unadoptedRrepositories struct {
repositories []string
index int
start int
end int
}
func (unadopted *unadoptedRepositories) add(repository string) {
func (unadopted *unadoptedRrepositories) add(repository string) {
if unadopted.index >= unadopted.start && unadopted.index < unadopted.end {
unadopted.repositories = append(unadopted.repositories, repository)
}
unadopted.index++
}
func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error {
func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRrepositories) error {
if len(repoNamesToCheck) == 0 {
return nil
}
@@ -265,7 +264,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad
}
for _, repoName := range repoNamesToCheck {
if !repoNames.Contains(repoName) {
unadopted.add(path.Join(userName, repoName)) // These are not used as filepaths - but as reponames - therefore use path.Join not filepath.Join
unadopted.add(filepath.Join(userName, repoName))
}
}
return nil
@@ -293,7 +292,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
var repoNamesToCheck []string
start := (opts.Page - 1) * opts.PageSize
unadopted := &unadoptedRepositories{
unadopted := &unadoptedRrepositories{
repositories: make([]string, 0, opts.PageSize),
start: start,
end: start + opts.PageSize,
+4 -4
View File
@@ -19,7 +19,7 @@ import (
func TestCheckUnadoptedRepositories_Add(t *testing.T) {
start := 10
end := 20
unadopted := &unadoptedRepositories{
unadopted := &unadoptedRrepositories{
start: start,
end: end,
index: 0,
@@ -39,7 +39,7 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
//
// Non existent user
//
unadopted := &unadoptedRepositories{start: 0, end: 100}
unadopted := &unadoptedRrepositories{start: 0, end: 100}
err := checkUnadoptedRepositories("notauser", []string{"repo"}, unadopted)
assert.NoError(t, err)
assert.Equal(t, 0, len(unadopted.repositories))
@@ -50,14 +50,14 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
userName := "user2"
repoName := "repo2"
unadoptedRepoName := "unadopted"
unadopted = &unadoptedRepositories{start: 0, end: 100}
unadopted = &unadoptedRrepositories{start: 0, end: 100}
err = checkUnadoptedRepositories(userName, []string{repoName, unadoptedRepoName}, unadopted)
assert.NoError(t, err)
assert.Equal(t, []string{path.Join(userName, unadoptedRepoName)}, unadopted.repositories)
//
// Existing (adopted) repository is not returned
//
unadopted = &unadoptedRepositories{start: 0, end: 100}
unadopted = &unadoptedRrepositories{start: 0, end: 100}
err = checkUnadoptedRepositories(userName, []string{repoName}, unadopted)
assert.NoError(t, err)
assert.Equal(t, 0, len(unadopted.repositories))
+5 -5
View File
@@ -32,16 +32,16 @@
{{end}}
{{end}}
{{if .IsFork}}
<span class="tooltip df" data-content="{{$.locale.Tr "repo.fork"}}" data-position="bottom center">{{svg "octicon-repo-forked"}}</span>
<span class="tooltip" data-content="{{$.locale.Tr "repo.fork"}}" data-position="bottom center">{{svg "octicon-repo-forked"}}</span>
{{else if .IsMirror}}
<span class="tooltip df" data-content="{{$.locale.Tr "mirror"}}" data-position="bottom center">{{svg "octicon-mirror"}}</span>
<span class="tooltip" data-content="{{$.locale.Tr "mirror"}}" data-position="bottom center">{{svg "octicon-mirror"}}</span>
{{end}}
</div>
</div>
<div class="metas df ac text grey">
<div class="metas df ac">
{{if .PrimaryLanguage}}
<a class="muted" href="{{$.Link}}?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}">
<span class="df ac mr-3"><i class="color-icon mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{.PrimaryLanguage.Language}}</span>
<a href="{{$.Link}}?q={{$.Keyword}}&sort={{$.SortType}}&language={{.PrimaryLanguage.Language}}">
<span class="text grey df ac mr-3"><i class="color-icon mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{.PrimaryLanguage.Language}}</span>
</a>
{{end}}
{{if not $.DisableStars}}
+2 -2
View File
@@ -68,8 +68,8 @@
<div class="item">
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong class="team-name">{{.Name}}</strong></a>
<p class="text grey">
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{$.locale.Tr "org.lower_members"}}</a> ·
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{$.locale.Tr "org.lower_repositories"}}</a>
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{$.locale.Tr "org.lower_members"}}</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{$.locale.Tr "org.lower_repositories"}}</a>
</p>
</div>
{{end}}
+1 -1
View File
@@ -11,7 +11,7 @@
<div class="ui top attached header comment-header df ac sb">
<div class="comment-header-left df ac">
{{if .OriginalAuthor}}
<span class="text black bold mr-2">
<span class="text black mr-2">
{{svg (MigrationIcon $.root.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
+2 -2
View File
@@ -30,7 +30,7 @@
<div class="ui top attached header comment-header df ac sb">
<div class="comment-header-left df ac">
{{if .Issue.OriginalAuthor}}
<span class="text black bold">
<span class="text black">
{{svg (MigrationIcon .Repository.GetOriginalURLHostname)}}
{{.Issue.OriginalAuthor}}
</span>
@@ -45,7 +45,7 @@
{{avatar .Issue.Poster}}
</a>
<span class="text grey">
{{template "shared/user/authorlink" .Issue.Poster}}
<a class="author"{{if gt .Issue.Poster.ID 0}} href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
{{.locale.Tr "repo.issues.commented_at" (.Issue.HashTag|Escape) $createdStr | Safe}}
</span>
{{end}}
@@ -25,7 +25,7 @@
<div class="ui top attached header comment-header df ac sb">
<div class="comment-header-left df ac">
{{if .OriginalAuthor}}
<span class="text black bold mr-2">
<span class="text black mr-2">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@@ -42,7 +42,9 @@
</a>
{{end}}
<span class="text grey">
{{template "shared/user/authorlink" .Poster}}
<a class="author"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
{{.Poster.GetDisplayName}}
</a>
{{$.locale.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}}
</span>
{{end}}
@@ -149,14 +151,14 @@
<span class="badge">{{svg "octicon-bookmark"}}</span>
{{template "shared/user/avatarlink" .Poster}}
{{if eq .RefAction 3}}<del>{{end}}
<span class="text grey muted-links">
<span class="text grey">
{{template "shared/user/authorlink" .Poster}}
{{$.locale.Tr $refTr (.EventTag|Escape) $createdStr (.RefCommentHTMLURL|Escape) $refFrom | Safe}}
</span>
{{if eq .RefAction 3}}</del>{{end}}
<div class="detail">
<span class="text grey"><a class="muted" href="{{.RefIssueHTMLURL}}"><b>{{.RefIssueTitle}}</b> {{.RefIssueIdent}}</a></span>
<span class="text grey"><a href="{{.RefIssueHTMLURL}}"><b>{{.RefIssueTitle}}</b> {{.RefIssueIdent}}</a></span>
</div>
</div>
{{else if eq .Type 4}}
@@ -205,7 +207,7 @@
{{if .RemovedAssignee}}
{{template "shared/user/avatarlink" .Assignee}}
<span class="text grey">
{{template "shared/user/authorlink" .Assignee}}
<a class="author" {{if gt .Assignee.ID 0}}href="{{.Assignee.HomeLink}}"{{end}}>{{.Assignee.GetDisplayName}}</a>
{{if eq .Poster.ID .Assignee.ID}}
{{$.locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}}
{{else}}
@@ -329,7 +331,7 @@
<div class="detail">
{{svg "octicon-plus"}}
<span class="text grey">
<a class="muted" href="{{.DependentIssue.HTMLURL}}">
<a href="{{.DependentIssue.HTMLURL}}">
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
{{else}}
@@ -352,7 +354,7 @@
<div class="detail">
<span class="text grey">{{svg "octicon-trash"}}</span>
<span class="text grey">
<a class="muted" href="{{.DependentIssue.HTMLURL}}">
<a href="{{.DependentIssue.HTMLURL}}">
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
{{else}}
@@ -406,7 +408,7 @@
<div class="comment-header-left df ac">
<span class="text grey">
{{if .OriginalAuthor}}
<span class="text black bold">
<span class="text black">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@@ -534,7 +536,7 @@
{{end}}
<span class="text grey">
{{if .OriginalAuthor}}
<span class="text black bold">
<span class="text black">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@@ -389,7 +389,7 @@
{{avatar $user}}
</a>
<div class="content">
{{template "shared/user/authorlink" $user}}
<a class="author">{{$user.DisplayName}}</a>
<div class="text">
{{$trackedtime}}
</div>
+1 -1
View File
@@ -160,7 +160,7 @@
</div>
<div class="issue-item-icon-right text grey">
{{if .NumComments}}
<a class="tdn muted" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
<a class="tdn" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{svg "octicon-comment" 16 "mr-2"}}{{.NumComments}}
</a>
{{end}}
+3 -1
View File
@@ -1 +1,3 @@
<a class="author text black bold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>
<a class="author"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{.GetDisplayName}}
</a>
+3 -1
View File
@@ -1 +1,3 @@
<a class="avatar"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{avatar .}}</a>
<a class="avatar"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{avatar .}}
</a>
+3 -1
View File
@@ -1 +1,3 @@
<a{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>
<a{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{.GetDisplayName}}
</a>
+5 -8
View File
@@ -16806,7 +16806,11 @@
"x-go-name": "FileName"
},
"labels": {
"$ref": "#/definitions/IssueTemplateLabels"
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "Labels"
},
"name": {
"type": "string",
@@ -16823,13 +16827,6 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"IssueTemplateLabels": {
"type": "array",
"items": {
"type": "string"
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"Label": {
"description": "Label a label to an issue or a pr",
"type": "object",
+4 -4
View File
@@ -29,7 +29,7 @@ func TestPackagePyPI(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "test-package"
packageVersion := "1!1.0.1+r1234"
packageVersion := "1.0.1"
packageAuthor := "KN4CK3R"
packageDescription := "Test Description"
@@ -72,7 +72,7 @@ func TestPackagePyPI(t *testing.T) {
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
assert.NoError(t, err)
assert.Nil(t, pd.SemVer)
assert.NotNil(t, pd.SemVer)
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
@@ -100,7 +100,7 @@ func TestPackagePyPI(t *testing.T) {
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
assert.NoError(t, err)
assert.Nil(t, pd.SemVer)
assert.NotNil(t, pd.SemVer)
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
@@ -164,7 +164,7 @@ func TestPackagePyPI(t *testing.T) {
nodes := htmlDoc.doc.Find("a").Nodes
assert.Len(t, nodes, 2)
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, regexp.QuoteMeta(packageName), regexp.QuoteMeta(packageVersion), hashSHA256))
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, packageName, packageVersion, hashSHA256))
for _, a := range nodes {
for _, att := range a.Attr {
+3
View File
@@ -26,6 +26,9 @@ function processWindowErrorEvent(e) {
return; // ignore such nonsense error event
}
// Wait for upstream fix: https://github.com/microsoft/monaco-editor/issues/2962
if (e.message.includes('Language id "vs.editor.nullLanguage" is not configured nor known')) return;
showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`);
}
-4
View File
@@ -99,10 +99,6 @@ export async function createMonaco(textarea, filename, editorOpts) {
}
});
// Quick fix: https://github.com/microsoft/monaco-editor/issues/2962
monaco.languages.register({id: 'vs.editor.nullLanguage'});
monaco.languages.setLanguageConfiguration('vs.editor.nullLanguage', {});
const editor = monaco.editor.create(container, {
value: textarea.value,
theme: 'gitea',
+1 -1
View File
@@ -37,7 +37,7 @@ export function initHeadNavbarContentToggle() {
export function initFootLanguageMenu() {
function linkLanguageAction() {
const $this = $(this);
$.get($this.data('url')).always(() => {
$.post($this.data('url')).always(() => {
window.location.reload();
});
}
+15 -22
View File
@@ -4,9 +4,6 @@ import {invertFileFolding} from './file-fold.js';
import {createTippy} from '../modules/tippy.js';
import {copyToClipboard} from './clipboard.js';
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
function changeHash(hash) {
if (window.history.pushState) {
window.history.pushState(null, null, hash);
@@ -138,7 +135,7 @@ export function initRepoCodeView() {
});
$(window).on('hashchange', () => {
let m = window.location.hash.match(rangeAnchorRegex);
let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
let $list;
if ($('div.blame').length) {
$list = $('.code-view td.lines-code.blame-code');
@@ -148,31 +145,27 @@ export function initRepoCodeView() {
let $first;
if (m) {
$first = $list.filter(`[rel=${m[1]}]`);
if ($first.length) {
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
// show code view menu marker (don't show in blame page)
if ($('div.blame').length === 0) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
return;
// show code view menu marker (don't show in blame page)
if ($('div.blame').length === 0) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
return;
}
m = window.location.hash.match(singleAnchorRegex);
m = window.location.hash.match(/^#(L|n)(\d+)$/);
if (m) {
$first = $list.filter(`[rel=L${m[2]}]`);
if ($first.length) {
selectRange($list, $first);
selectRange($list, $first);
// show code view menu marker (don't show in blame page)
if ($('div.blame').length === 0) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
// show code view menu marker (don't show in blame page)
if ($('div.blame').length === 0) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
}
}).trigger('hashchange');
}
-18
View File
@@ -1,18 +0,0 @@
import {test, expect} from 'vitest';
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.js';
test('singleAnchorRegex', () => {
expect(singleAnchorRegex.test('#L0')).toEqual(false);
expect(singleAnchorRegex.test('#L1')).toEqual(true);
expect(singleAnchorRegex.test('#L01')).toEqual(false);
expect(singleAnchorRegex.test('#n0')).toEqual(false);
expect(singleAnchorRegex.test('#n1')).toEqual(true);
expect(singleAnchorRegex.test('#n01')).toEqual(false);
});
test('rangeAnchorRegex', () => {
expect(rangeAnchorRegex.test('#L0-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L10')).toEqual(true);
expect(rangeAnchorRegex.test('#L01-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L01')).toEqual(false);
});
-1
View File
@@ -2,7 +2,6 @@ import {isDarkTheme} from '../utils.js';
const {mermaidMaxSourceCharacters} = window.config;
const iframeCss = `
:root {color-scheme: normal}
body {margin: 0; padding: 0}
#mermaid {display: block; margin: 0 auto}
`;
+70 -22
View File
@@ -79,7 +79,6 @@
--color-pink: #e03997;
--color-brown: #a5673f;
--color-grey: #888888;
--color-black: #1b1c1d;
/* light variants - produced via Sass scale-color(color, $lightness: +25%) */
--color-red-light: #e45e5e;
--color-orange-light: #f59555;
@@ -93,9 +92,9 @@
--color-pink-light: #e86bb1;
--color-brown-light: #c58b66;
--color-grey-light: #a6a6a6;
--color-black-light: #525558;
/* other colors */
--color-gold: #a1882b;
--color-black: #1b1c1d;
--color-white: #ffffff;
--color-diff-removed-word-bg: #fdb8c0;
--color-diff-added-word-bg: #acf2bd;
@@ -169,7 +168,6 @@
--color-active-line: #fffbdd;
accent-color: var(--color-accent);
color-scheme: light;
}
:root * {
@@ -294,15 +292,13 @@ a,
text-decoration-skip-ink: all;
}
a.muted,
.muted-links a {
a.muted {
color: inherit;
}
a:hover,
a.muted:hover,
a.muted:hover [class*="color-text"],
.muted-links a:hover,
.ui.breadcrumb a:hover {
color: var(--color-primary);
}
@@ -1304,22 +1300,6 @@ a.ui.card:hover,
visibility: hidden;
}
.text.red { color: var(--color-red) !important; }
.text.orange { color: var(--color-orange) !important; }
.text.yellow { color: var(--color-yellow) !important; }
.text.olive { color: var(--color-olive) !important; }
.text.green { color: var(--color-green) !important; }
.text.teal { color: var(--color-teal) !important; }
.text.blue { color: var(--color-blue) !important; }
.text.violet { color: var(--color-violet) !important; }
.text.purple { color: var(--color-purple) !important; }
.text.pink { color: var(--color-pink) !important; }
.text.brown { color: var(--color-brown) !important; }
.text.black { color: var(--color-text) !important; }
.text.grey { color: var(--color-text-light) !important; }
.text.light.grey { color: var(--color-grey-light) !important; }
.text.gold { color: var(--color-gold) !important; }
.ui {
&.left:not(.action) {
float: left;
@@ -1389,6 +1369,74 @@ a.ui.card:hover,
}
.text {
&.red {
color: var(--color-red) !important;
a {
color: inherit !important;
&:hover {
color: var(--color-red-light) !important;
}
}
}
&.blue {
color: var(--color-blue) !important;
a {
color: inherit !important;
&:hover {
color: var(--color-blue-light) !important;
}
}
}
&.black {
color: var(--color-text);
&:hover {
color: var(--color-text-dark);
}
}
&.grey {
color: var(--color-text-light) !important;
a {
color: var(--color-text) !important;
&:hover {
color: var(--color-primary) !important;
}
}
}
&.light.grey {
color: var(--color-text-light-2) !important;
}
&.green {
color: var(--color-green) !important;
}
&.purple {
color: var(--color-purple) !important;
}
&.yellow {
color: var(--color-yellow) !important;
}
&.orange {
color: var(--color-orange) !important;
}
&.gold {
color: var(--color-gold) !important;
}
&.left {
text-align: left !important;
}
+11 -7
View File
@@ -829,7 +829,7 @@
.timeline-avatar {
position: absolute;
left: -68px;
left: -72px;
img {
width: 40px !important;
@@ -846,6 +846,7 @@
.avatar img {
width: 20px;
height: 20px;
margin: 0 .25rem;
vertical-align: middle;
}
@@ -980,6 +981,10 @@
margin-top: 4px;
}
.author {
font-weight: 600;
}
.comment-form-reply .footer {
padding-bottom: 1em;
}
@@ -1160,12 +1165,9 @@
padding-left: 15px;
.detail {
margin-top: 4px;
margin-left: 14px;
.svg {
margin-right: 2px;
}
font-size: .9rem;
margin-top: 5px;
margin-left: 8px;
}
.segments {
@@ -2671,10 +2673,12 @@
a {
color: var(--color-text);
text-decoration: none;
}
a:hover {
color: var(--color-primary);
text-decoration: none;
}
}
-1
View File
@@ -537,7 +537,6 @@
width: 100%;
height: var(--height-loading); // actual height is set in JS after loading
overflow: hidden;
color-scheme: normal; // match the value inside the iframe to allow it to become transparent
}
.markup-block-error {
-8
View File
@@ -91,14 +91,6 @@
border-radius: 3px;
vertical-align: 2px !important;
}
progress::-webkit-progress-value {
background-color: var(--color-secondary-dark-4);
}
progress::-moz-progress-bar {
background-color: var(--color-secondary-dark-4);
}
}
.conflicting {
+25 -25
View File
@@ -56,35 +56,34 @@
--color-secondary-alpha-80: #454a57cc;
--color-secondary-alpha-90: #454a57e1;
/* colors */
--color-red: #cc4848;
--color-red: #7d3434;
--color-orange: #cc580c;
--color-yellow: #cc9903;
--color-olive: #91a313;
--color-green: #87ab63;
--color-teal: #00918a;
--color-blue: #3a8ac6;
--color-violet: #906ae1;
--color-purple: #b259d0;
--color-pink: #d22e8b;
--color-brown: #a47252;
--color-grey: #9ea2aa;
--color-black: #1e222e;
/* light variants - produced via Sass scale-color(color, $lightness: -10%) */
--color-red-light: #c23636;
--color-orange-light: #b84f0b;
--color-yellow-light: #b88a03;
--color-olive-light: #839311;
--color-green-light: #7a9e55;
--color-teal-light: #00837c;
--color-blue-light: #347cb3;
--color-violet-light: #7b4edb;
--color-purple-light: #a742c9;
--color-pink-light: #be297d;
--color-brown-light: #94674a;
--color-grey-light: #8d919b;
--color-black-light: #1b1f29;
--color-blue: #1a6aa6;
--color-violet: #502aa1;
--color-purple: #8229a0;
--color-pink: #c21e7b;
--color-brown: #845232;
--color-grey: #5e626a;
/* light variants */
--color-red-light: #984646;
--color-orange-light: #e6630d;
--color-yellow-light: #e5ac04;
--color-olive-light: #a3b816;
--color-green-light: #9fbc82;
--color-teal-light: #00a39c;
--color-blue-light: #1e78bb;
--color-violet-light: #5a30b5;
--color-purple-light: #932eb4;
--color-pink-light: #db228a;
--color-brown-light: #955d39;
--color-grey-light: #6a6e78;
/* other colors */
--color-gold: #b1983b;
--color-black: #1e222e;
--color-gold: #a1882b;
--color-white: #ffffff;
--color-diff-removed-word-bg: #6f3333;
--color-diff-added-word-bg: #3c653c;
@@ -154,9 +153,10 @@
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-active-line: #534d1b;
}
accent-color: var(--color-accent);
color-scheme: dark;
::-webkit-calendar-picker-indicator {
filter: invert(.8);
}
/* invert emojis that are hard to read otherwise */