Compare commits

...

30 Commits

Author SHA1 Message Date
techknowlogick 9bccc60cf5
add changelog for 1.18.0-rc1 (#21829)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-24 19:49:22 +08:00
Xinyu Zhou 16772ffde3
Fix flex layout for repo list icons (#21896) (#21920)
Backport #21896

#20241 Added a tooltip, which does not satisfy the flex layout, and the
icons are not aligned

Signed-off-by: Xinyu Zhou <i@sourcehut.net>
2022-11-24 00:44:07 -06:00
Xinyu Zhou c844c4ff88
Fix vertical align of committer avatar rendered by email address (#21884) (#21918)
Backport #21884

Committer avatar rendered by `func AvatarByEmail` are not vertical align
as `func Avatar` does.

- Replace literals `ui avatar` and `ui avatar vm` with the constant
`DefaultAvatarClass`

Signed-off-by: Xinyu Zhou <i@sourcehut.net>
2022-11-24 10:52:20 +08:00
KN4CK3R f4ec03a4e5
Fix setting HTTP headers after write (#21833) (#21877)
Backport of #21833

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-11-22 09:00:42 +08:00
KN4CK3R b2369830bb
Do not allow Ghost access to limited visible user/org (#21849) (#21876)
Backport of #21849
2022-11-20 19:37:20 +00:00
silverwind ef08998bf6
Color and Style enhancements (#21784, #21799) (#21868)
Backport #21784
Backport #21799

These PRs provide tweaks and simplification to the less/css selectors, simplifying text color selectors and tweak arc-green colors with a follow-up to adjust the timeline 

See the original PRs for more details
2022-11-20 10:47:02 +00:00
Jason Song 7a004ad7eb
Support comma-delimited string as labels in issue template (#21831) (#21873)
Backport #21831.

The [labels in issue YAML templates](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax)
can be a string array or a comma-delimited string, so a single string
should be valid labels.

The old codes committed in #20987 ignore this, that's why the warning is
displayed:

<img width="618" alt="image" src="https://user-images.githubusercontent.com/9418365/202112642-93dc72d0-71c3-40a2-9720-30fc2d48c97c.png">

Fixes #17877.
2022-11-20 10:44:20 +00:00
Gusted af8b2250c4
Prevent dangling user redirects (#21856) (#21858)
- Backport #21856
- It's possible that the `user_redirect` table contains a user id that
no longer exists.
  - Delete a user redirect upon deleting the user.
- Add a check for these dangling user redirects to check-db-consistency.
2022-11-18 22:25:00 +08:00
Jason Song 8917af8701
Ignore issue template with a special name (#21830) (#21835)
Backport #21830.

A file in `ISSUE_TEMPLATE` with the name `config.yml` shouldn't be
treated as a YAML template, it's for [configuring the template
chooser](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser).

The old code tried to ignore the file, but it didn't work, caused by
#20987. That's why the warning is displayed:

<img width="415" alt="image"

src="https://user-images.githubusercontent.com/9418365/202094067-804c42fe-0e9e-4fc5-bf01-d95fa336f54f.png">

Note that this PR is not an implementation of `config.yml`, there will
be another one to do it.
2022-11-16 14:48:33 -05:00
zeripath 0d25292fbc
Prevent panic in doctor command when running default checks (#21791) (#21807)
Backport #21791

There was a bug introduced in #21352 due to a change of behaviour caused
by #19280. This causes a panic on running the default doctor checks
because the panic introduced by #19280 assumes that the only way
opts.StdOut and opts.Stderr can be set in RunOpts is deliberately.
Unfortunately, when running a git.Command the provided RunOpts can be
set, therefore if you share a common set of RunOpts these two values can
be set by the previous commands.

This PR stops using common RunOpts for the commands in that doctor check
but secondly stops RunCommand variants from changing the provided
RunOpts.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-11-14 10:58:32 +08:00
Jason Song ac409fcfba
Load GitRepo in API before deleting issue (#21720) (#21796)
Backport #21720.

Fix #20921.

The `ctx.Repo.GitRepo` has been used in deleting issues when the issue
is a PR.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
2022-11-13 00:54:24 -05:00
Gusted df512f77b7
Upgrade golang.org/x/crypto (#21792) (#21793)
- Backport #21792
- Update the crypto dependency to include
6fad3dfc18
  - Resolves #17798
2022-11-12 22:15:21 -06:00
silverwind e4bf9cad1e
Ignore line anchor links with leading zeroes (#21728) (#21776)
Backport #21728
Fixes: https://github.com/go-gitea/gitea/issues/21722

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-11 21:14:16 +08:00
Jason Song 169eeee101
Set last login when activating account (#21731) (#21755)
Backport #21731.

Fix #21698.

Set the last login time to the current time when activating the user
successfully.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-11 01:26:17 -05:00
wxiaoguang 3aacc9b4ac
Revert unrelated changes for SMTP auth (#21767) (#21768)
Backport #21767

The purpose of #18982 is to improve the SMTP mailer, but there were some
unrelated changes made to the SMTP auth in
d60c438694

This PR reverts these unrelated changes, fix #21744

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-10 16:11:56 -05:00
wxiaoguang 87d05d376d
Init git module before database migration (#21764) (#21765)
Backport #21764

Some database migrations depend on the git module.
2022-11-10 14:22:41 +00:00
Lunny Xiao b9dcf991b9
Fix dashboard ignored system setting cache (#21621) (#21759)
backport #21621

This is a performance regression from #18058

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Andrew Thornton <art27@cantab.net>
2022-11-10 19:41:44 +08:00
Xinyu Zhou a2a42cd5de
Fix UI language switching bug (#21597) (#21749)
Backport #21597

Related:
* https://github.com/go-gitea/gitea/pull/21596#issuecomment-1291450224

There was a bug when switching language by AJAX: the irrelevant POST
requests were processed by the target page's handler.

Now, use GET instead of POST. The GET requests should be harmless.

Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-11-10 10:14:32 +08:00
Wayne Starr 805a14cc91
Remove semver compatible flag and change pypi to an array of test cases (#21708) (#21730)
Backport (#21708)

This addresses #21707 and adds a second package test case for a
non-semver compatible version (this might be overkill though since you
could also edit the old package version to have an epoch in front and
see the error, this just seemed more flexible for the future).

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2022-11-09 23:02:11 +08:00
Xinyu Zhou 69a54545a8
Quick fixes monaco-editor error: "vs.editor.nullLanguage" (#21734) (#21738)
Backport #21734

fixes: https://github.com/go-gitea/gitea/issues/21733

Uncaught Error: Language id "vs.editor.nullLanguage" is not configured
nor known

Note that this monaco-editor worked fine on 0.33.0 and broke on 0.34.0.
If upstream fixed, remove this code.

Signed-off-by: Xinyu Zhou <i@sourcehut.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-09 21:19:44 +08:00
Wayne Starr e054f80fe0
Allow local package identifiers for PyPI packages (#21690) (#21727)
Backport (#21690)

Fixes #21683

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2022-11-09 09:10:06 +08:00
wxiaoguang 89d52922d0
Fix token generation when using INTERNAL_TOKEN_URI (#21669) (#21670)
Backport #21669

Fix https://github.com/go-gitea/gitea/issues/21666
Caused by https://github.com/go-gitea/gitea/pull/19663

Before: when install, the INTERNAL_TOKEN was always generated and saved.
But the internal token may be already there by INTERNAL_TOKEN_URI

After: INTERNAL_TOKEN_URI file must be non-empty. When install, skip
internal token generation if the token exists.
2022-11-03 20:54:25 +00:00
zeripath 3a0d000b94
Fix repository adoption on Windows (#21646) (#21650)
Backport #21646

A bug was introduced in #17865 where filepath.Join is used to join
putative unadopted repository owner and names together. This is
incorrect as these names are then used as repository names - which shoud
have the '/' separator. This means that adoption will not work on
Windows servers.

Fix #21632

Signed-off-by: Andrew Thornton <art27@cantab.net>
2022-11-01 22:32:03 +00:00
silverwind fd4e7447e7
Fix opaque background on mermaid diagrams (#21642) (#21652)
Backport #21642

Browsers introduce a opaque background on iframes if the iframe
element's color-scheme does not match the document's color scheme which
in case of a dark theme results in a mismatch and the browser adds a
white background. Avoid this by specifying the same color scheme outside
and inside the iframe.

See https://fvsch.com/transparent-iframes for more info.

My initial attempt was to make the iframe document the same color-scheme
as the parent page (light or dark) but with that, there was a ugly
background flash on load in Chrome because Chrome apparently always
loads iframe in light scheme initially. Firefox still shows a background
flash on load but this is not possible to get rid of and it's certainly
a browser bug.

Before:
<img width="1147" alt="Screen Shot 2022-10-31 at 13 30 55"
src="https://user-images.githubusercontent.com/115237/199017132-9828aace-bdd0-4ede-8118-359e72bcf2fe.png">

After:
<img width="1152" alt="Screen Shot 2022-10-31 at 13 30 36"
src="https://user-images.githubusercontent.com/115237/199017137-989a9e67-3fe0-445f-a191-df5bf290dabf.png">
2022-11-01 22:31:17 +00:00
Jason Song 7a8e34b255
Deal with markdown template without metadata (#21639) (#21654)
Backport #21639 .

Fixed #21636.

Related to #20987.

A markdown template without metadata should not be treated as an invalid
template.

And this PR fixed another bug that non-template files(neither .md nor
.yaml) are treated as yaml files.

<img width="504" alt="image"

src="https://user-images.githubusercontent.com/9418365/198968668-40082fa1-4f25-4d3e-9b73-1dbf6d1a7521.png">
2022-11-01 23:41:31 +08:00
Jason Song e4a10f8c78
Sync git hooks when config file path changed (#21619) (#21626)
Backport #21619 .

A patch to #17335.

Just like AppPath, Gitea writes its own CustomConf into git hook scripts
too. If Gitea's CustomConf changes, then the git push may fail.

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: zeripath <art27@cantab.net>
2022-10-30 11:17:11 +08:00
silverwind 6dba648e5d
Use CSS color-scheme instead of invert (#21616) (#21623)
Backport #21616 to 1.18

The
[`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme)
property changes the base color of certain form elements like the
datepicker icon in Chrome. Set it and remove the previous invert hack.

Before with invert removed:
<img width="840" alt="Screen Shot 2022-10-27 at 11 42 54"
src="https://user-images.githubusercontent.com/115237/198251927-b742e14e-0c62-492c-b667-ee6c69de4ad8.png">
<img width="238" alt="Screen Shot 2022-10-27 at 12 23 28"
src="https://user-images.githubusercontent.com/115237/198260413-37c1ca85-c2de-4c09-8b37-6aa8a23ab575.png">

After:
<img width="841" alt="Screen Shot 2022-10-27 at 11 43 05"
src="https://user-images.githubusercontent.com/115237/198251934-568fa291-0d18-4cd4-adec-58ae1ad90ab2.png">
<img width="839" alt="Screen Shot 2022-10-27 at 11 44 36"
src="https://user-images.githubusercontent.com/115237/198251936-a435105e-572b-41f6-8262-a53820f1d364.png">
<img width="243" alt="Screen Shot 2022-10-27 at 12 23 42"
src="https://user-images.githubusercontent.com/115237/198260432-5eaffc82-ffb8-4559-b1c2-08a39e8f4427.png">

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-10-29 13:24:57 +08:00
KN4CK3R 4d39fd8aae
Fix `Timestamp.IsZero` (#21593) (#21603)
Backport of #21593

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-10-27 16:47:06 +08:00
wxiaoguang 4869f9c3c8
Revert: auto generate INTERNAL_TOKEN (#21608) (#21609)
Backport #21608

Follow #19663

Some users do cluster deployment, they still depend on this
auto-generating behavior.
2022-10-27 11:17:47 +08:00
qwerty287 79275d9db4
Fix 500 on PR files API (#21602) (#21607)
Fixes an 500 error/panic if using the changed PR files API with pages
that should return empty lists because there are no items anymore.
`start-end` is then < 0 which ends in panic.

Backport https://github.com/go-gitea/gitea/pull/21602

<!--

Please check the following:

1. Make sure you are targeting the `main` branch, pull requests on
release branches are only allowed for bug fixes.
2. Read contributing guidelines:
https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md
3. Describe what your pull request does and which issue you're targeting
(if any)

-->

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: delvh <dev.lh@web.de>
2022-10-26 19:21:08 +03:00
73 changed files with 1202 additions and 594 deletions

View File

@ -4,6 +4,182 @@ 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

View File

@ -413,9 +413,9 @@ var (
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
cli.StringFlag{
Name: "addr",
Name: "host",
Value: "",
Usage: "SMTP Addr",
Usage: "SMTP Host",
},
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("addr") {
conf.Addr = c.String("addr")
if c.IsSet("host") {
conf.Host = c.String("host")
}
if c.IsSet("port") {
conf.Port = c.Int("port")

8
go.mod
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.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.0.0-20220927171203-f486391704dc
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891
golang.org/x/net v0.2.0
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
golang.org/x/text v0.3.8
golang.org/x/sys v0.2.0
golang.org/x/text v0.4.0
golang.org/x/tools v0.1.12
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0

18
go.sum
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.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/crypto v0.2.1-0.20221112162523-6fad3dfc1891 h1:WhEPFM1Ck5gaKybeSWvzI7Y/cd8K9K5tJGRxXMACOBA=
golang.org/x/crypto v0.2.1-0.20221112162523-6fad3dfc1891/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -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.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -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.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -20,8 +20,12 @@ 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
const DefaultAvatarPixelSize = 28
DefaultAvatarPixelSize = 28
)
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
type EmailHash struct {
@ -150,10 +154,11 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink()
}
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
enableFederatedAvatar := enableFederatedAvatarSetting.GetValueBool()
var err error
if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
if enableFederatedAvatar && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email)
if final {
// for final link, we can spend more time on slow external query
@ -171,8 +176,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return urlStr
}
disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatar != nil && !disableGravatar.GetValueBool() {
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
disableGravatar := disableGravatarSetting.GetValueBool()
if !disableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))

View File

@ -14,6 +14,7 @@ import (
"regexp"
"strings"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@ -513,6 +514,13 @@ 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())

View File

@ -458,8 +458,9 @@ func CountOrgs(opts FindOrgOptions) (int64, error) {
// HasOrgOrUserVisible tells if the given user can see the given org or user
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
// Not SignedUser
if user == nil {
// If user is nil, it's an anonymous user/request.
// The Ghost user is handled like an anonymous user.
if user == nil || user.IsGhost() {
return orgOrUser.Visibility == structs.VisibleTypePublic
}

View File

@ -12,6 +12,7 @@ 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"
@ -35,6 +36,10 @@ func (s *Setting) TableName() string {
}
func (s *Setting) GetValueBool() bool {
if s == nil {
return false
}
b, _ := strconv.ParseBool(s.SettingValue)
return b
}
@ -75,8 +80,8 @@ func IsErrDataExpired(err error) bool {
return ok
}
// GetSetting returns specific setting
func GetSetting(key string) (*Setting, error) {
// GetSettingNoCache returns specific setting without using the cache
func GetSettingNoCache(key string) (*Setting, error) {
v, err := GetSettings([]string{key})
if err != nil {
return nil, err
@ -87,6 +92,24 @@ func GetSetting(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++ {
@ -139,12 +162,13 @@ 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 := GetSetting(key)
s, err := GetSettingNoCache(key)
if IsErrSettingIsNotExist(err) {
return SetSetting(&Setting{
SettingKey: key,
@ -160,9 +184,13 @@ func SetSettingNoVersion(key, value string) error {
// SetSetting updates a users' setting for a specific key
func SetSetting(setting *Setting) error {
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
_, err := cache.Set(genSettingCacheKey(setting.SettingKey), func() (*Setting, error) {
return setting, upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version)
})
if err != nil {
return err
}
setting.Version++
return nil
}
@ -213,7 +241,7 @@ var (
func Init() error {
var disableGravatar bool
disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
disableGravatarSetting, err := GetSettingNoCache(KeyPictureDisableGravatar)
if IsErrSettingIsNotExist(err) {
disableGravatar = setting.GetDefaultDisableGravatar()
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
@ -224,7 +252,7 @@ func Init() error {
}
var enableFederatedAvatar bool
enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
enableFederatedAvatarSetting, err := GetSettingNoCache(KeyPictureEnableFederatedAvatar)
if IsErrSettingIsNotExist(err) {
enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}

View File

@ -9,3 +9,8 @@ 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
}

View File

@ -89,6 +89,7 @@ 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)
}

View File

@ -68,11 +68,9 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false
autoGenerateAvatar := false
var disableGravatar bool
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatarSetting != nil {
disableGravatar = disableGravatarSetting.GetValueBool()
}
disableGravatar := disableGravatarSetting.GetValueBool()
switch {
case u.UseCustomAvatar:

View File

@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/cache"
"xorm.io/builder"
)
@ -47,9 +48,25 @@ func IsErrUserSettingIsNotExist(err error) bool {
return ok
}
// GetSetting returns specific setting
// genSettingCacheKey returns the cache key for some configuration
func genSettingCacheKey(userID int64, key string) string {
return fmt.Sprintf("user_%d.setting.%s", userID, key)
}
// GetSetting returns the setting value via the key
func GetSetting(uid int64, key string) (*Setting, error) {
v, err := GetUserSettings(uid, []string{key})
return cache.Get(genSettingCacheKey(uid, key), func() (*Setting, error) {
res, err := GetSettingNoCache(uid, key)
if err != nil {
return nil, err
}
return res, nil
})
}
// GetSettingNoCache returns specific setting without using the cache
func GetSettingNoCache(uid int64, key string) (*Setting, error) {
v, err := GetSettings(uid, []string{key})
if err != nil {
return nil, err
}
@ -59,8 +76,8 @@ func GetSetting(uid int64, key string) (*Setting, error) {
return v[key], nil
}
// GetUserSettings returns specific settings from user
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
// GetSettings returns specific settings from user
func GetSettings(uid int64, keys []string) (map[string]*Setting, error) {
settings := make([]*Setting, 0, len(keys))
if err := db.GetEngine(db.DefaultContext).
Where("user_id=?", uid).
@ -105,6 +122,7 @@ func GetUserSetting(userID int64, key string, def ...string) (string, error) {
if err := validateUserSettingKey(key); err != nil {
return "", err
}
setting := &Setting{UserID: userID, SettingKey: key}
has, err := db.GetEngine(db.DefaultContext).Get(setting)
if err != nil {
@ -124,7 +142,10 @@ func DeleteUserSetting(userID int64, key string) error {
if err := validateUserSettingKey(key); err != nil {
return err
}
cache.Remove(genSettingCacheKey(userID, key))
_, err := db.GetEngine(db.DefaultContext).Delete(&Setting{UserID: userID, SettingKey: key})
return err
}
@ -133,7 +154,12 @@ func SetUserSetting(userID int64, key, value string) error {
if err := validateUserSettingKey(key); err != nil {
return err
}
return upsertUserSettingValue(userID, key, value)
_, err := cache.Set(genSettingCacheKey(userID, key), func() (string, error) {
return value, upsertUserSettingValue(userID, key, value)
})
return err
}
func upsertUserSettingValue(userID int64, key, value string) error {

View File

@ -27,7 +27,7 @@ func TestSettings(t *testing.T) {
assert.NoError(t, err)
// get specific setting
settings, err := user_model.GetUserSettings(99, []string{keyName})
settings, err := user_model.GetSettings(99, []string{keyName})
assert.NoError(t, err)
assert.Len(t, settings, 1)
assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue)

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.GetUserSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
settings, err = user_model.GetSettings(user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem})
if err != nil {
return
} else if len(settings) == 0 {

131
modules/cache/cache.go vendored
View File

@ -46,32 +46,64 @@ func GetCache() mc.Cache {
return conn
}
// Get returns the key value from cache with callback when no key exists in cache
func Get[V interface{}](key string, getFunc func() (V, error)) (V, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
cached := conn.Get(key)
if value, ok := cached.(V); ok {
return value, nil
}
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
// Set updates and returns the key value in the cache with callback. The old value is only removed if the updateFunc() is successful
func Set[V interface{}](key string, valueFunc func() (V, error)) (V, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return valueFunc()
}
value, err := valueFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
// GetString returns the key value from cache with callback when no key exists in cache
func GetString(key string, getFunc func() (string, error)) (string, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
if !conn.IsExist(key) {
var (
value string
err error
)
if value, err = getFunc(); err != nil {
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return "", err
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
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", conn.Get(key)), nil
return fmt.Sprintf("%s", cached), nil
}
// GetInt returns key value from cache with callback when no key exists in cache
@ -79,30 +111,33 @@ func GetInt(key string, getFunc func() (int, error)) (int, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
if !conn.IsExist(key) {
var (
value int
err error
)
if value, err = getFunc(); err != nil {
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return 0, err
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
}
switch value := conn.Get(key).(type) {
switch v := cached.(type) {
case int:
return value, nil
return v, nil
case string:
v, err := strconv.Atoi(value)
value, err := strconv.Atoi(v)
if err != nil {
return 0, err
}
return v, nil
return value, nil
default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
}
@ -111,30 +146,34 @@ func GetInt64(key string, getFunc func() (int64, error)) (int64, error) {
if conn == nil || setting.CacheService.TTL == 0 {
return getFunc()
}
if !conn.IsExist(key) {
var (
value int64
err error
)
if value, err = getFunc(); err != nil {
cached := conn.Get(key)
if cached == nil {
value, err := getFunc()
if err != nil {
return value, err
}
err = conn.Put(key, value, setting.CacheService.TTLSeconds())
if err != nil {
return 0, err
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
}
switch value := conn.Get(key).(type) {
switch v := conn.Get(key).(type) {
case int64:
return value, nil
return v, nil
case string:
v, err := strconv.ParseInt(value, 10, 64)
value, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
return v, nil
return value, nil
default:
return 0, fmt.Errorf("Unsupported cached value type: %v", value)
value, err := getFunc()
if err != nil {
return value, err
}
return value, conn.Put(key, value, setting.CacheService.TTLSeconds())
}
}

View File

@ -34,6 +34,7 @@ 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"
@ -322,9 +323,9 @@ func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
if statusPrefix == 4 || statusPrefix == 5 {
log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
}
ctx.Resp.WriteHeader(status)
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
ctx.Resp.WriteHeader(status)
if _, err := ctx.Resp.Write(bs); err != nil {
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
}
@ -345,36 +346,55 @@ 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(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")
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)
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
ctx.SetServeHeaders(name)
ctx.SetServeHeaders(&ServeHeaderOptions{
Filename: 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) {

View File

@ -205,6 +205,9 @@ 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 {

View File

@ -19,11 +19,9 @@ func synchronizeRepoHeads(ctx context.Context, logger log.Logger, autofix bool)
numReposUpdated := 0
err := iterateRepositories(ctx, func(repo *repo_model.Repository) error {
numRepos++
runOpts := &git.RunOpts{Dir: repo.RepoPath()}
_, _, defaultBranchErr := git.NewCommand(ctx, "rev-parse").AddDashesAndList(repo.DefaultBranch).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)
head, _, headErr := git.NewCommand(ctx, "symbolic-ref", "--short", "HEAD").RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
// what we expect: default branch is valid, and HEAD points to it
if headErr == nil && defaultBranchErr == nil && head == repo.DefaultBranch {
@ -49,7 +47,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", repo.DefaultBranch).Run(runOpts)
err := git.NewCommand(ctx, "symbolic-ref").AddDashesAndList("HEAD", git.BranchPrefix+repo.DefaultBranch).Run(&git.RunOpts{Dir: repo.RepoPath()})
if err != nil {
logger.Warn("Failed to fix HEAD for %s/%s: %v", repo.OwnerName, repo.Name, err)
return nil
@ -65,7 +63,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")
logger.Info("All %d repos have their HEADs in the correct state", numRepos)
} else {
if numHeadsBroken == 0 && numDefaultBranchesBroken != 0 {
logger.Critical("Default branches are broken for %d/%d repos", numDefaultBranchesBroken, numRepos)

View File

@ -202,8 +202,11 @@ func (c *Command) Run(opts *RunOpts) error {
if opts == nil {
opts = &RunOpts{}
}
if opts.Timeout <= 0 {
opts.Timeout = defaultCommandExecutionTimeout
// We must not change the provided options
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultCommandExecutionTimeout
}
if len(opts.Dir) == 0 {
@ -238,7 +241,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, opts.Timeout, desc)
ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
}
defer finished()
@ -339,9 +342,20 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
}
stdoutBuf := &bytes.Buffer{}
stderrBuf := &bytes.Buffer{}
opts.Stdout = stdoutBuf
opts.Stderr = stderrBuf
err := c.Run(opts)
// We must not change the provided options as it could break future calls - therefore make a copy.
newOpts := &RunOpts{
Env: opts.Env,
Timeout: opts.Timeout,
UseContextTimeout: opts.UseContextTimeout,
Dir: opts.Dir,
Stdout: stdoutBuf,
Stderr: stderrBuf,
Stdin: opts.Stdin,
PipelineFunc: opts.PipelineFunc,
}
err := c.Run(newOpts)
stderr = stderrBuf.Bytes()
if err != nil {
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}

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[interface{}]interface{})
opt, ok := option.(map[string]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[interface{}]interface{}); ok {
if vs, ok := o.data.(map[string]interface{}); ok {
if v, ok := vs["label"].(string); ok {
return v
}

View File

@ -6,17 +6,20 @@ 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
}{
{
@ -316,21 +319,9 @@ body:
`,
wantErr: "body[0](checkboxes), option[0]: 'required' should be a bool",
},
}
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: "valid",
content: `
name: Name
title: Title
about: About
@ -386,8 +377,8 @@ body:
required: false
- label: Option 3 of checkboxes
required: true
`
want := &api.IssueTemplate{
`,
want: &api.IssueTemplate{
Name: "Name",
Title: "Title",
About: "About",
@ -454,29 +445,160 @@ body:
"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},
map[string]interface{}{"label": "Option 1 of checkboxes", "required": true},
map[string]interface{}{"label": "Option 2 of checkboxes", "required": false},
map[string]interface{}{"label": "Option 3 of checkboxes", "required": true},
},
},
},
},
FileName: "test.yaml",
},
wantErr: "",
},
{
name: "single label",
content: `
name: Name
title: Title
about: About
labels: label1
ref: Ref
body:
- type: markdown
id: id1
attributes:
value: Value of the markdown
`,
want: &api.IssueTemplate{
Name: "Name",
Title: "Title",
About: "About",
Labels: []string{"label1"},
Ref: "Ref",
Fields: []*api.IssueFormField{
{
Type: "markdown",
ID: "id1",
Attributes: map[string]interface{}{
"value": "Value of the markdown",
},
},
},
FileName: "test.yaml",
},
wantErr: "",
},
{
name: "comma-delimited labels",
content: `
name: Name
title: Title
about: About
labels: label1,label2,,label3 ,,
ref: Ref
body:
- type: markdown
id: id1
attributes:
value: Value of the markdown
`,
want: &api.IssueTemplate{
Name: "Name",
Title: "Title",
About: "About",
Labels: []string{"label1", "label2", "label3"},
Ref: "Ref",
Fields: []*api.IssueFormField{
{
Type: "markdown",
ID: "id1",
Attributes: map[string]interface{}{
"value": "Value of the markdown",
},
},
},
FileName: "test.yaml",
},
wantErr: "",
},
{
name: "empty string as labels",
content: `
name: Name
title: Title
about: About
labels: ''
ref: Ref
body:
- type: markdown
id: id1
attributes:
value: Value of the markdown
`,
want: &api.IssueTemplate{
Name: "Name",
Title: "Title",
About: "About",
Labels: nil,
Ref: "Ref",
Fields: []*api.IssueFormField{
{
Type: "markdown",
ID: "id1",
Attributes: map[string]interface{}{
"value": "Value of the markdown",
},
},
},
FileName: "test.yaml",
},
wantErr: "",
},
{
name: "comma delimited labels in markdown",
filename: "test.md",
content: `---
name: Name
title: Title
about: About
labels: label1,label2,,label3 ,,
ref: Ref
---
Content
`,
want: &api.IssueTemplate{
Name: "Name",
Title: "Title",
About: "About",
Labels: []string{"label1", "label2", "label3"},
Ref: "Ref",
Fields: nil,
Content: "Content\n",
FileName: "test.md",
},
wantErr: "",
},
}
got, err := unmarshal("test.yaml", []byte(content))
if err != nil {
t.Fatal(err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filename := "test.yaml"
if tt.filename != "" {
filename = tt.filename
}
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)
tmpl, err := unmarshal(filename, []byte(tt.content))
require.NoError(t, err)
if tt.wantErr != "" {
require.EqualError(t, Validate(tmpl), tt.wantErr)
} else {
require.NoError(t, Validate(tmpl))
want, _ := json.Marshal(tt.want)
got, _ := json.Marshal(tmpl)
require.JSONEq(t, string(want), string(got))
}
})
}
}
func TestRenderToMarkdown(t *testing.T) {
type args struct {

View File

@ -14,8 +14,9 @@ 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.v2"
"gopkg.in/yaml.v3"
)
// CouldBe indicates a file with the filename could be a template,
@ -95,16 +96,29 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
}{}
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
templateBody, err := markdown.ExtractMetadata(string(content), it)
if err != nil {
return nil, err
}
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
}
}
}
} else if typ == api.IssueTemplateTypeYaml {
if err := yaml.Unmarshal(content, it); err != nil {
return nil, fmt.Errorf("yaml unmarshal: %w", err)

View File

@ -9,82 +9,86 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
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,
IssueTemplate is a legacy to keep the unit tests working.
Copied from structs.IssueTemplate, the original type has been changed a lot to support yaml template.
*/
type IssueTemplate struct {
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"`
Labels []string `json:"labels" yaml:"labels"`
Ref string `json:"ref" yaml:"ref"`
}
func (it *IssueTemplate) Valid() bool {
return strings.TrimSpace(it.Name) != "" && strings.TrimSpace(it.About) != ""
}
func TestExtractMetadata(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
assert.NoError(t, err)
assert.Equal(t, bodyTest, body)
assert.Equal(t, metaTest, meta)
assert.True(t, validateMetadata(meta))
assert.True(t, meta.Valid())
})
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoLastSeparator", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
_, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoBody", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
assert.NoError(t, err)
assert.Equal(t, "", body)
assert.Equal(t, metaTest, meta)
assert.True(t, validateMetadata(meta))
assert.True(t, meta.Valid())
})
}
func TestExtractMetadataBytes(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
assert.NoError(t, err)
assert.Equal(t, bodyTest, string(body))
assert.Equal(t, metaTest, meta)
assert.True(t, validateMetadata(meta))
assert.True(t, meta.Valid())
})
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
assert.Error(t, err)
})
t.Run("NoLastSeparator", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
assert.Error(t, err)
})
t.Run("NoBody", func(t *testing.T) {
var meta structs.IssueTemplate
var meta IssueTemplate
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
assert.NoError(t, err)
assert.Equal(t, "", string(body))
assert.Equal(t, metaTest, meta)
assert.True(t, validateMetadata(meta))
assert.True(t, meta.Valid())
})
}
@ -97,7 +101,7 @@ labels:
- bug
- "test label"`
bodyTest = "This is the body"
metaTest = structs.IssueTemplate{
metaTest = IssueTemplate{
Name: "Test",
About: "A Test",
Title: "Test Title",

View File

@ -22,6 +22,7 @@ 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"
@ -962,6 +963,11 @@ 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 {
@ -1150,6 +1156,8 @@ 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()
@ -1173,7 +1181,15 @@ func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string {
if err != nil {
log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err)
}
return strings.TrimSpace(string(buf))
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
// only file URIs are allowed
default:
@ -1182,6 +1198,19 @@ 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, "/"))

View File

@ -5,8 +5,12 @@
package structs
import (
"path/filepath"
"fmt"
"path"
"strings"
"time"
"gopkg.in/yaml.v3"
)
// StateType issue state type
@ -146,13 +150,46 @@ 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 []string `json:"labels" yaml:"labels"`
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())
}
// IssueTemplateType defines issue template type
type IssueTemplateType string
@ -163,14 +200,14 @@ const (
// Type returns the type of IssueTemplate, can be "md", "yaml" or empty for known
func (it IssueTemplate) Type() IssueTemplateType {
if it.Name == "config.yaml" || it.Name == "config.yml" {
if base := path.Base(it.FileName); base == "config.yaml" || base == "config.yml" {
// ignore config.yaml which is a special configuration file
return ""
}
if ext := filepath.Ext(it.FileName); ext == ".md" {
if ext := path.Ext(it.FileName); ext == ".md" {
return IssueTemplateTypeMarkdown
} else if ext == ".yaml" || ext == ".yml" {
return "yaml"
}
return IssueTemplateTypeYaml
}
return ""
}

View File

@ -0,0 +1,106 @@
// 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)
}
})
}
}

View File

@ -7,6 +7,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"`
}
// Name returns the item name

View File

@ -1,46 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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,
})
}

View File

@ -1,34 +0,0 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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)
}

View File

@ -42,7 +42,6 @@ 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"
@ -87,7 +86,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion
},
"DisableGravatar": func() bool {
return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
return system_model.GetSettingBool(system_model.KeyPictureDisableGravatar)
},
"DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName
@ -647,7 +646,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, "ui avatar vm", others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
switch t := item.(type) {
case *user_model.User:
@ -678,7 +677,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, "ui avatar", others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
src := repo.RelAvatarLink()
if src != "" {
@ -689,7 +688,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, "ui avatar", others...)
size, class := parseOthers(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
if src != "" {

View File

@ -103,5 +103,5 @@ func (ts TimeStamp) FormatDate() string {
// IsZero is zero time
func (ts TimeStamp) IsZero() bool {
return ts.AsTimeInLocation(time.Local).IsZero()
return int64(ts) == 0
}

View File

@ -21,12 +21,19 @@ import (
packages_service "code.gitea.io/gitea/services/packages"
)
// https://www.python.org/dev/peps/pep-0503/#normalized-names
// https://peps.python.org/pep-0426/#name
var normalizer = strings.NewReplacer(".", "-", "_", "-")
var nameMatcher = regexp.MustCompile(`\A[a-zA-Z0-9\.\-_]+\z`)
var nameMatcher = regexp.MustCompile(`\A(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9])\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]*))?$`)
// 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`)
func apiError(ctx *context.Context, status int, obj interface{}) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
@ -121,7 +128,7 @@ func UploadPackageFile(ctx *context.Context) {
packageName := normalizer.Replace(ctx.Req.FormValue("name"))
packageVersion := ctx.Req.FormValue("version")
if !nameMatcher.MatchString(packageName) || !versionMatcher.MatchString(packageVersion) {
if !isValidNameAndVersion(packageName, packageVersion) {
apiError(ctx, http.StatusBadRequest, "invalid name or version")
return
}
@ -139,7 +146,7 @@ func UploadPackageFile(ctx *context.Context) {
Name: packageName,
Version: packageVersion,
},
SemverCompatible: true,
SemverCompatible: false,
Creator: ctx.Doer,
Metadata: &pypi_module.Metadata{
Author: ctx.Req.FormValue("author"),
@ -170,3 +177,7 @@ func UploadPackageFile(ctx *context.Context) {
ctx.Status(http.StatusCreated)
}
func isValidNameAndVersion(packageName, packageVersion string) bool {
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
}

View File

@ -0,0 +1,39 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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"))
}

View File

@ -77,7 +77,9 @@ func enumeratePackages(ctx *context.Context, filename string, pvs []*packages_mo
})
}
ctx.SetServeHeaders(filename + ".gz")
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: filename + ".gz",
})
zw := gzip.NewWriter(ctx.Resp)
defer zw.Close()
@ -115,7 +117,9 @@ func ServePackageSpecification(ctx *context.Context) {
return
}
ctx.SetServeHeaders(filename)
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: filename,
})
zw := zlib.NewWriter(ctx.Resp)
defer zw.Close()

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(), repo.DeleteIssue)
Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue)
m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)

View File

@ -1443,7 +1443,11 @@ func GetPullRequestFiles(ctx *context.APIContext) {
end = totalNumberOfFiles
}
apiFiles := make([]*api.ChangedFile, 0, end-start)
lenFiles := end - start
if lenFiles < 0 {
lenFiles = 0
}
apiFiles := make([]*api.ChangedFile, 0, lenFiles)
for i := start; i < end; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
}

View File

@ -7,7 +7,6 @@ package common
import (
"fmt"
"io"
"net/url"
"path"
"path/filepath"
"strings"
@ -53,50 +52,44 @@ 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)
}
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(fileName))
mimeType = setting.MimeTypeMap.Map[fileExtension]
opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}
if mimeType == "" {
sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")
if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(filePath))
opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
}
if opts.ContentType == "" {
if sniffedType.IsBrowsableBinaryType() {
mimeType = sniffedType.GetMimeType()
opts.ContentType = sniffedType.GetMimeType()
} else if isPlain {
mimeType = "text/plain"
opts.ContentType = "text/plain"
} else {
mimeType = typesniffer.ApplicationOctetStream
opts.ContentType = 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
@ -109,16 +102,12 @@ 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'")
}
disposition := "inline"
opts.Disposition = "inline"
if isSVG && !setting.UI.SVG.Enabled {
disposition = "attachment"
opts.Disposition = "attachment"
}
// 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")
ctx.SetServeHeaders(opts)
_, err = ctx.Resp.Write(buf)
if err != nil {

View File

@ -76,21 +76,31 @@ func InitGitServices() {
mustInit(repo_service.Init)
}
func syncAppPathForGit(ctx context.Context) error {
func syncAppConfForGit(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
@ -153,7 +163,7 @@ func GlobalInitInstalled(ctx context.Context) {
mustInit(repo_migrations.Init)
eventsource.GetManager().Init()
mustInitCtx(ctx, syncAppPathForGit)
mustInitCtx(ctx, syncAppConfForGit)
mustInit(ssh.Init)

View File

@ -473,12 +473,16 @@ 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)
}
// 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 == "" {

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,
Addr: form.SMTPAddr,
Host: form.SMTPHost,
Port: form.SMTPPort,
AllowedDomains: form.AllowedDomains,
ForceSMTPS: form.ForceSMTPS,

View File

@ -18,7 +18,6 @@ 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"
@ -203,7 +202,11 @@ func ChangeConfig(ctx *context.Context) {
value := ctx.FormString("value")
version := ctx.FormInt("version")
if err := system_module.SetSetting(key, value, version); err != nil {
if err := system_model.SetSetting(&system_model.Setting{
SettingKey: key,
SettingValue: value,
Version: 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),

View File

@ -783,6 +783,13 @@ 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 + "/")
}

View File

@ -5,7 +5,6 @@
package feed
import (
"net/http"
"time"
activities_model "code.gitea.io/gitea/models/activities"
@ -59,7 +58,6 @@ 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 {

View File

@ -597,7 +597,10 @@ func RegisterRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/favicon.ico", func(ctx *context.Context) {
ctx.ServeFile(path.Join(setting.StaticRootPath, "public/img/favicon.png"))
ctx.SetServeHeaders(&context.ServeHeaderOptions{
Filename: "favicon.png",
})
http.ServeFile(ctx.Resp, ctx.Req, path.Join(setting.StaticRootPath, "public/img/favicon.png"))
})
m.Group("/{username}", func() {
m.Get(".png", func(ctx *context.Context) { ctx.Error(http.StatusNotFound) })

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.Addr,
ServerName: source.Host,
}
conn, err := net.Dial("tcp", net.JoinHostPort(source.Addr, strconv.Itoa(source.Port)))
conn, err := net.Dial("tcp", net.JoinHostPort(source.Host, 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.Addr)
client, err := smtp.NewClient(conn, source.Host)
if err != nil {
return fmt.Errorf("failed to create NewClient: %w", err)
}

View File

@ -19,7 +19,7 @@ import (
// Source holds configuration for the SMTP login source.
type Source struct {
Auth string
Addr string
Host string
Port int
AllowedDomains string `xorm:"TEXT"`
ForceSMTPS bool

View File

@ -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.Addr)
auth = smtp.PlainAuth("", userName, password, source.Host)
case LoginAuthentication:
auth = &loginAuthenticator{userName, password}
case CRAMMD5Authentication:

View File

@ -45,7 +45,7 @@ type AuthenticationForm struct {
IsActive bool
IsSyncEnabled bool
SMTPAuth string
SMTPAddr string
SMTPHost string
SMTPPort int
AllowedDomains string
SecurityProtocol int `binding:"Range(0,2)"`

View File

@ -8,6 +8,7 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
@ -218,21 +219,21 @@ func DeleteUnadoptedRepository(doer, u *user_model.User, repoName string) error
return util.RemoveAll(repoPath)
}
type unadoptedRrepositories struct {
type unadoptedRepositories struct {
repositories []string
index int
start int
end int
}
func (unadopted *unadoptedRrepositories) add(repository string) {
func (unadopted *unadoptedRepositories) 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 *unadoptedRrepositories) error {
func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error {
if len(repoNamesToCheck) == 0 {
return nil
}
@ -264,7 +265,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad
}
for _, repoName := range repoNamesToCheck {
if !repoNames.Contains(repoName) {
unadopted.add(filepath.Join(userName, repoName))
unadopted.add(path.Join(userName, repoName)) // These are not used as filepaths - but as reponames - therefore use path.Join not filepath.Join
}
}
return nil
@ -292,7 +293,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in
var repoNamesToCheck []string
start := (opts.Page - 1) * opts.PageSize
unadopted := &unadoptedRrepositories{
unadopted := &unadoptedRepositories{
repositories: make([]string, 0, opts.PageSize),
start: start,
end: start + opts.PageSize,

View File

@ -19,7 +19,7 @@ import (
func TestCheckUnadoptedRepositories_Add(t *testing.T) {
start := 10
end := 20
unadopted := &unadoptedRrepositories{
unadopted := &unadoptedRepositories{
start: start,
end: end,
index: 0,
@ -39,7 +39,7 @@ func TestCheckUnadoptedRepositories(t *testing.T) {
//
// Non existent user
//
unadopted := &unadoptedRrepositories{start: 0, end: 100}
unadopted := &unadoptedRepositories{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 = &unadoptedRrepositories{start: 0, end: 100}
unadopted = &unadoptedRepositories{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 = &unadoptedRrepositories{start: 0, end: 100}
unadopted = &unadoptedRepositories{start: 0, end: 100}
err = checkUnadoptedRepositories(userName, []string{repoName}, unadopted)
assert.NoError(t, err)
assert.Equal(t, 0, len(unadopted.repositories))

View File

@ -32,16 +32,16 @@
{{end}}
{{end}}
{{if .IsFork}}
<span class="tooltip" data-content="{{$.locale.Tr "repo.fork"}}" data-position="bottom center">{{svg "octicon-repo-forked"}}</span>
<span class="tooltip df" data-content="{{$.locale.Tr "repo.fork"}}" data-position="bottom center">{{svg "octicon-repo-forked"}}</span>
{{else if .IsMirror}}
<span class="tooltip" data-content="{{$.locale.Tr "mirror"}}" data-position="bottom center">{{svg "octicon-mirror"}}</span>
<span class="tooltip df" data-content="{{$.locale.Tr "mirror"}}" data-position="bottom center">{{svg "octicon-mirror"}}</span>
{{end}}
</div>
</div>
<div class="metas df ac">
<div class="metas df ac text grey">
{{if .PrimaryLanguage}}
<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 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>
{{end}}
{{if not $.DisableStars}}

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 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>
<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>
</p>
</div>
{{end}}

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 mr-2">
<span class="text black bold mr-2">
{{svg (MigrationIcon $.root.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>

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">
<span class="text black bold">
{{svg (MigrationIcon .Repository.GetOriginalURLHostname)}}
{{.Issue.OriginalAuthor}}
</span>
@ -45,7 +45,7 @@
{{avatar .Issue.Poster}}
</a>
<span class="text grey">
<a class="author"{{if gt .Issue.Poster.ID 0}} href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.GetDisplayName}}</a>
{{template "shared/user/authorlink" .Issue.Poster}}
{{.locale.Tr "repo.issues.commented_at" (.Issue.HashTag|Escape) $createdStr | Safe}}
</span>
{{end}}

View File

@ -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 mr-2">
<span class="text black bold mr-2">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@ -42,9 +42,7 @@
</a>
{{end}}
<span class="text grey">
<a class="author"{{if gt .Poster.ID 0}} href="{{.Poster.HomeLink}}"{{end}}>
{{.Poster.GetDisplayName}}
</a>
{{template "shared/user/authorlink" .Poster}}
{{$.locale.Tr "repo.issues.commented_at" (.HashTag|Escape) $createdStr | Safe}}
</span>
{{end}}
@ -151,14 +149,14 @@
<span class="badge">{{svg "octicon-bookmark"}}</span>
{{template "shared/user/avatarlink" .Poster}}
{{if eq .RefAction 3}}<del>{{end}}
<span class="text grey">
<span class="text grey muted-links">
{{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 href="{{.RefIssueHTMLURL}}"><b>{{.RefIssueTitle}}</b> {{.RefIssueIdent}}</a></span>
<span class="text grey"><a class="muted" href="{{.RefIssueHTMLURL}}"><b>{{.RefIssueTitle}}</b> {{.RefIssueIdent}}</a></span>
</div>
</div>
{{else if eq .Type 4}}
@ -207,7 +205,7 @@
{{if .RemovedAssignee}}
{{template "shared/user/avatarlink" .Assignee}}
<span class="text grey">
<a class="author" {{if gt .Assignee.ID 0}}href="{{.Assignee.HomeLink}}"{{end}}>{{.Assignee.GetDisplayName}}</a>
{{template "shared/user/authorlink" .Assignee}}
{{if eq .Poster.ID .Assignee.ID}}
{{$.locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}}
{{else}}
@ -331,7 +329,7 @@
<div class="detail">
{{svg "octicon-plus"}}
<span class="text grey">
<a href="{{.DependentIssue.HTMLURL}}">
<a class="muted" href="{{.DependentIssue.HTMLURL}}">
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
{{else}}
@ -354,7 +352,7 @@
<div class="detail">
<span class="text grey">{{svg "octicon-trash"}}</span>
<span class="text grey">
<a href="{{.DependentIssue.HTMLURL}}">
<a class="muted" href="{{.DependentIssue.HTMLURL}}">
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
#{{.DependentIssue.Index}} {{.DependentIssue.Title}}
{{else}}
@ -408,7 +406,7 @@
<div class="comment-header-left df ac">
<span class="text grey">
{{if .OriginalAuthor}}
<span class="text black">
<span class="text black bold">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>
@ -536,7 +534,7 @@
{{end}}
<span class="text grey">
{{if .OriginalAuthor}}
<span class="text black">
<span class="text black bold">
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname)}}
{{.OriginalAuthor}}
</span>

View File

@ -389,7 +389,7 @@
{{avatar $user}}
</a>
<div class="content">
<a class="author">{{$user.DisplayName}}</a>
{{template "shared/user/authorlink" $user}}
<div class="text">
{{$trackedtime}}
</div>

View File

@ -160,7 +160,7 @@
</div>
<div class="issue-item-icon-right text grey">
{{if .NumComments}}
<a class="tdn" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
<a class="tdn muted" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{svg "octicon-comment" 16 "mr-2"}}{{.NumComments}}
</a>
{{end}}

View File

@ -1,3 +1 @@
<a class="author"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{.GetDisplayName}}
</a>
<a class="author text black bold muted"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>

View File

@ -1,3 +1 @@
<a class="avatar"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{avatar .}}
</a>
<a class="avatar"{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{avatar .}}</a>

View File

@ -1,3 +1 @@
<a{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>
{{.GetDisplayName}}
</a>
<a{{if gt .ID 0}} href="{{.HomeLink}}"{{end}}>{{.GetDisplayName}}</a>

View File

@ -16806,11 +16806,7 @@
"x-go-name": "FileName"
},
"labels": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "Labels"
"$ref": "#/definitions/IssueTemplateLabels"
},
"name": {
"type": "string",
@ -16827,6 +16823,13 @@
},
"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",

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.0.1"
packageVersion := "1!1.0.1+r1234"
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.NotNil(t, pd.SemVer)
assert.Nil(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.NotNil(t, pd.SemVer)
assert.Nil(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, packageName, packageVersion, hashSHA256))
hrefMatcher := regexp.MustCompile(fmt.Sprintf(`%s/files/%s/%s/test\..+#sha256-%s`, root, regexp.QuoteMeta(packageName), regexp.QuoteMeta(packageVersion), hashSHA256))
for _, a := range nodes {
for _, att := range a.Attr {

View File

@ -26,9 +26,6 @@ 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.`);
}

View File

@ -99,6 +99,10 @@ 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',

View File

@ -37,7 +37,7 @@ export function initHeadNavbarContentToggle() {
export function initFootLanguageMenu() {
function linkLanguageAction() {
const $this = $(this);
$.post($this.data('url')).always(() => {
$.get($this.data('url')).always(() => {
window.location.reload();
});
}

View File

@ -4,6 +4,9 @@ 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);
@ -135,7 +138,7 @@ export function initRepoCodeView() {
});
$(window).on('hashchange', () => {
let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
let m = window.location.hash.match(rangeAnchorRegex);
let $list;
if ($('div.blame').length) {
$list = $('.code-view td.lines-code.blame-code');
@ -145,6 +148,7 @@ export function initRepoCodeView() {
let $first;
if (m) {
$first = $list.filter(`[rel=${m[1]}]`);
if ($first.length) {
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
// show code view menu marker (don't show in blame page)
@ -155,9 +159,11 @@ export function initRepoCodeView() {
$('html, body').scrollTop($first.offset().top - 200);
return;
}
m = window.location.hash.match(/^#(L|n)(\d+)$/);
}
m = window.location.hash.match(singleAnchorRegex);
if (m) {
$first = $list.filter(`[rel=L${m[2]}]`);
if ($first.length) {
selectRange($list, $first);
// show code view menu marker (don't show in blame page)
@ -167,6 +173,7 @@ export function initRepoCodeView() {
$('html, body').scrollTop($first.offset().top - 200);
}
}
}).trigger('hashchange');
}
$(document).on('click', '.fold-file', ({currentTarget}) => {

View File

@ -0,0 +1,18 @@
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);
});

View File

@ -2,6 +2,7 @@ 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}
`;

View File

@ -79,6 +79,7 @@
--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;
@ -92,9 +93,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;
@ -168,6 +169,7 @@
--color-active-line: #fffbdd;
accent-color: var(--color-accent);
color-scheme: light;
}
:root * {
@ -292,13 +294,15 @@ a,
text-decoration-skip-ink: all;
}
a.muted {
a.muted,
.muted-links a {
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);
}
@ -1300,6 +1304,22 @@ 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;
@ -1369,74 +1389,6 @@ 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;
}

View File

@ -829,7 +829,7 @@
.timeline-avatar {
position: absolute;
left: -72px;
left: -68px;
img {
width: 40px !important;
@ -846,7 +846,6 @@
.avatar img {
width: 20px;
height: 20px;
margin: 0 .25rem;
vertical-align: middle;
}
@ -981,10 +980,6 @@
margin-top: 4px;
}
.author {
font-weight: 600;
}
.comment-form-reply .footer {
padding-bottom: 1em;
}
@ -1165,9 +1160,12 @@
padding-left: 15px;
.detail {
font-size: .9rem;
margin-top: 5px;
margin-left: 8px;
margin-top: 4px;
margin-left: 14px;
.svg {
margin-right: 2px;
}
}
.segments {
@ -2673,12 +2671,10 @@
a {
color: var(--color-text);
text-decoration: none;
}
a:hover {
color: var(--color-primary);
text-decoration: none;
}
}

View File

@ -537,6 +537,7 @@
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 {

View File

@ -91,6 +91,14 @@
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 {

View File

@ -56,34 +56,35 @@
--color-secondary-alpha-80: #454a57cc;
--color-secondary-alpha-90: #454a57e1;
/* colors */
--color-red: #7d3434;
--color-red: #cc4848;
--color-orange: #cc580c;
--color-yellow: #cc9903;
--color-olive: #91a313;
--color-green: #87ab63;
--color-teal: #00918a;
--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-blue: #3a8ac6;
--color-violet: #906ae1;
--color-purple: #b259d0;
--color-pink: #d22e8b;
--color-brown: #a47252;
--color-grey: #9ea2aa;
--color-black: #1e222e;
--color-gold: #a1882b;
/* 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;
/* other colors */
--color-gold: #b1983b;
--color-white: #ffffff;
--color-diff-removed-word-bg: #6f3333;
--color-diff-added-word-bg: #3c653c;
@ -153,10 +154,9 @@
--color-accent: var(--color-primary-light-1);
--color-small-accent: var(--color-primary-light-5);
--color-active-line: #534d1b;
}
::-webkit-calendar-picker-indicator {
filter: invert(.8);
accent-color: var(--color-accent);
color-scheme: dark;
}
/* invert emojis that are hard to read otherwise */