Compare commits

...

95 Commits

Author SHA1 Message Date
wxiaoguang
c97b89a662 Fix bug when viewing the commit diff page with non-ANSI files (#36149) (#36150)
Backport #36149
2025-12-14 01:14:06 +08:00
Giteabot
2dd8ef8368 Fix various bugs (#36139) (#36151)
Backport #36139

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-12-13 13:31:49 +00:00
Giteabot
432e128074 Hide RSS icon when viewing a file not under a branch (#36135) (#36141)
Backport #36135 by @lunny

Fix #35855

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-12-12 18:39:16 +01:00
silverwind
8d6442a43e Fix SVG size calulation, only use style attribute (#36133) (#36134)
Backport of https://github.com/go-gitea/gitea/pull/36133, only the
bugfix part.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-12-12 09:40:27 +02:00
wxiaoguang
3d66e75a47 Make Golang correctly delete temp files during uploading (#36128) (#36129)
Fix #36127

Partially backport #36128
And by the way partially backport  #36017
2025-12-11 20:10:59 +01:00
Giteabot
e98d9bb93e Improve math rendering (#36124) (#36125)
Backport #36124 by @wxiaoguang

Fix #36108
Fix #36107

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-12-11 01:40:01 +08:00
Giteabot
a601c09826 Changed a small typo in an error message and code comments. (#36117) (#36123)
Backport #36117 by @schinkelg

Changed a small typo in an English error message and code comments. Very
small PR.

Co-authored-by: Ger Schinkel <schinkelg@users.noreply.github.com>
2025-12-10 18:45:56 +08:00
Giteabot
b5f50ff63b Add strikethrough button to markdown editor (#36087) (#36104)
Backport #36087 by silverwind

Co-authored-by: silverwind <me@silverwind.io>
2025-12-08 09:07:27 +08:00
Giteabot
b1b35e934e Fix the bug when ssh clone with redirect user or repository (#36039) (#36090)
Backport #36039 by @lunny

Fix #36026 

The redirect should be checked when original user/repo doesn't exist.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-12-04 20:06:14 -08:00
Giteabot
544450a212 fix some file icon ui (#36078) (#36088)
Backport #36078 by @a1012112796

fix #36071

looks that's because if an svg in hiden env, it's color added by
`fill="url(#a)"` will become not usefull. by ai helping, I think moving
it out of page by position is a good solution. fell free creat a new
pull request if you have a better soluton. Thanks.
<img width="2198" height="1120" alt="image"
src="https://github.com/user-attachments/assets/bbf7c171-0b7f-412a-a1bc-aea3f1629636"
/>

Signed-off-by: a1012112796 <1012112796@qq.com>
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-12-04 19:39:35 +00:00
Giteabot
0ab447005d Use Golang net/smtp instead of gomail's smtp to send email (#36055) (#36083)
Backport #36055 by @lunny

Replace #36032
Fix #36030

This PR use `net/smtp` instead of gomail's smtp. Now
github.com/wneessen/go-mail will be used only for generating email
message body.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-12-04 18:58:01 +00:00
Giteabot
52902d4ece Fix edit user email bug in API (#36068) (#36081)
Backport #36068 by @lunny

Follow #36058 for API edit user bug when editing email.

- The Admin Edit User API includes a breaking change. Previously, when
updating a user with an email from an unallowed domain, the request
would succeed but return a warning in the response headers. Now, the
request will fail and return an error in the response body instead.
- Removed `AdminAddOrSetPrimaryEmailAddress` because it will not be used
any where.

Fix https://github.com/go-gitea/gitea/pull/36058#issuecomment-3600005186

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-12-04 18:25:52 +00:00
silverwind
0e91c8a068 Bump toolchain to go1.25.5, misc fixes (#36082)
Backport toolchain change into 1.25. This is needed because of the
govulncheck issue
[present](https://github.com/go-gitea/gitea/actions/runs/19921920886/job/57112316941)
in the branch.

---------

Signed-off-by: silverwind <me@silverwind.io>
2025-12-04 17:57:59 +00:00
Giteabot
45cdc5d8fd Fix bug when updating user email (#36058) (#36066)
Backport #36058 by @lunny

Fix #20390 

We should use `ReplacePrimaryEmailAddress` instead of
`AdminAddOrSetPrimaryEmailAddress` when modify user's email from admin
panel. And also we need a database transaction to keep deletion and
insertion succeed at the same time.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-12-02 02:57:21 +01:00
Zettat123
b276849cd8 Fix Actions pull_request.paths being triggered incorrectly by rebase (#36045) (#36054)
Backport #36045

Partially fix #34710

The bug described in #34710 can be divided into two parts: `push.paths`
and `pull_request.paths`. This PR fixes the issue related to
`pull_request.paths`. The root cause is that the check for whether the
workflow can be triggered happens **before** updating the PR’s merge
base. This causes the file-change detection to use the old merge base.
Therefore, we need to update the merge base first and then check whether
the workflow can be triggered.
2025-11-29 05:45:30 +00:00
Giteabot
46d1d154e8 Fix error handling in mailer and wiki services (#36041) (#36053)
Backport #36041 by @hamkido

- Updated error message in `incoming.go` to remove unnecessary wrapping
of the error.
- Corrected typo in error message in `wiki.go` for clarity.

Co-authored-by: hamkido <hamki.do2000@gmail.com>
2025-11-28 20:34:38 -08:00
Giteabot
f164e38e04 Fix incorrect viewed files counter if file has changed (#36009) (#36047)
Backport #36009 by @bytedream

File changes since last review didn't decrease the viewed files counter

---
<img width="440" height="178" alt="image"
src="https://github.com/user-attachments/assets/da34fcf4-452f-4f71-8da2-97edbfc31fdd"
/>

Also reported here ->
https://github.com/go-gitea/gitea/issues/35803#issuecomment-3567850285

Co-authored-by: bytedream <me@bytedream.dev>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-28 11:33:37 +01:00
Giteabot
d4d338f1c1 Fix container registry error handling (#36021) (#36037)
Backport #36021 by wxiaoguang

1. the `if` check in `handleCreateManifestResult` didn't handler err
correctly
2. add more error details for debugging

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-26 08:01:35 -08:00
Giteabot
f6895f632e Add "site admin" back to profile menu (#36010) (#36013)
Backport #36010 by @wxiaoguang

Fix #35904

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-23 14:07:44 -08:00
Lunny Xiao
eaa916a786 release notes for 1.25.2 (#35986)
~Wait #35988~

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-22 11:05:00 -08:00
Lunny Xiao
91901c2a60 Allow empty commit when merging pull request with squash style (#35989) (#36003) 2025-11-22 09:17:28 -08:00
Lunny Xiao
20cf4b7849 Fix various permission & login related bugs (#36002) (#36004)
Backport #36002 

Permission & protection check:

- Fix Delete Release permission check
- Fix Update Pull Request with rebase branch protection check
- Fix Issue Dependency permission check
- Fix Delete Comment History ID check

Information leaking:

- Show unified message for non-existing user and invalid password
    - Fix #35984
- Don't expose release draft to non-writer users.
- Make API returns signature's email address instead of the user
profile's.

Auth & Login:

- Avoid GCM OAuth2 attempt when OAuth2 is disabled
    - Fix #35510

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-22 12:33:48 +00:00
Lunny Xiao
5e7207d428 upgrade golang.org/x/crypto to 0.45.0 (#35988) 2025-11-20 17:25:35 -05:00
Giteabot
e3bfee80dd Change project default column icon to 'star' (#35967) (#35979)
Backport #35967 by @DrMaxNix

Consistently use a `star` icon to highlight the default column of a
project.
The icon is both shown while viewing the project, as well as while
changing the default status of this column.

<img width="1065" height="370" alt="image"
src="https://github.com/user-attachments/assets/1ca5773d-8eec-4b90-ad0b-22b1f4bd4cfd"
/>

Co-authored-by: DrMaxNix <git@drmaxnix.de>
2025-11-19 10:43:48 +01:00
Giteabot
f93e2cf301 Misc CSS fixes (#35888) (#35981)
Backport #35888 by @silverwind

Fixes: https://github.com/go-gitea/gitea/issues/35913
Fixes: https://github.com/go-gitea/gitea/issues/35942

Contains a number of minor CSS fixes.

Fix missing border on targeted speech bubble
<img width="158" height="90" alt="Screenshot 2025-11-06 at 22 43 31"
src="https://github.com/user-attachments/assets/94696191-353a-4782-a998-2a3d5552ab71"
/>

Add padding to inline comments, slightly more padding around emoji
button
<img width="823" height="301" alt="Screenshot 2025-11-06 at 22 38 39"
src="https://github.com/user-attachments/assets/3ed8f113-13d2-4fad-9d12-81a670540e0b"
/>

Center text on header in code search results
<img width="1328" height="295" alt="Screenshot 2025-11-06 at 22 08 01"
src="https://github.com/user-attachments/assets/41e3d279-8504-4435-9347-e9b969cdfaa2"
/>

Tweak emoji selector, reducing font size primarily
<img width="251" height="219" alt="Screenshot 2025-11-06 at 22 29 46"
src="https://github.com/user-attachments/assets/e892646e-129f-44fd-8333-7a8e14863f03"
/>

Minor tweaks to repo sidebar, reduce font size by 1px, center "Release"
text with label.
<img width="390" height="586" alt="image"
src="https://github.com/user-attachments/assets/397dc36b-11e6-42df-bcdf-e97f4280a90e"
/>

Fix issue comment buttons being misaligned on mobile
<img width="757" height="160" alt="Screenshot 2025-11-06 at 22 50 19"
src="https://github.com/user-attachments/assets/1609d104-1bfe-4913-bfa0-6f4739716d61"
/>

Add highlight to actions re-run icon
<img width="116" height="106" alt="Screenshot 2025-11-06 at 23 04 30"
src="https://github.com/user-attachments/assets/0daed0ac-ef59-432a-b534-b4256d38393b"
/>

Fix actions re-run button overflow
<img width="214" height="125" alt="image"
src="https://github.com/user-attachments/assets/d5c79ce6-ad31-4c06-a411-d79eefb72d02"
/>

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-19 08:16:54 +01:00
Giteabot
1b01d6de82 Fix container push tag overwriting (#35936) (#35954)
Backport #35936 by wxiaoguang

Fix #35853

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-14 12:22:23 +08:00
Giteabot
d67cd622d0 Fix corrupted external render content (#35946) (#35950)
Backport #35946 by wxiaoguang

Fix #35944

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-14 02:15:36 +00:00
Giteabot
15f3e9d5a5 Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
Backport #35937 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-12 23:31:05 +00:00
Giteabot
01fa8b2b7e Limit read bytes instead of ReadAll (#35928) (#35934)
Backport #35928 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-13 02:26:27 +08:00
Giteabot
1d9ae7ac23 Load jQuery as early as possible to support custom scripts (#35926) (#35929)
Backport #35926 by wxiaoguang

Fix #35923

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-12 07:24:21 +08:00
Giteabot
01873a99c1 Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
Backport #35882 by lifegpc

Co-authored-by: lifegpc <g1710431395@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-11 02:24:06 +00:00
Giteabot
ce70863793 Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
Backport #35894 by zorrobiwan

Signed-off-by: Alberty Pascal <github@albertyorban.be>
Co-authored-by: Alberty Pascal <github@albertyorban.be>
2025-11-11 01:39:35 +00:00
Giteabot
327f2207dc Make OAuth2 issuer configurable (#35915) (#35916)
Backport #35915 by wxiaoguang
2025-11-10 16:12:25 +00:00
Giteabot
db876d8f17 Fix #35763: Add proper page title for project pages (#35773) (#35909)
Backport #35773 by @mithileshgupta12

Co-authored-by: Mithilesh Gupta <mithileshgupta059@gmail.com>
Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
2025-11-10 09:42:14 +02:00
Giteabot
2b71bf283b Display source code downloads last for release attachments (#35897) (#35903)
Backport #35897 by lutinglt

Typically, you want to download the binaries, not the source code.

Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-09 14:51:36 +08:00
Giteabot
1ca4fef611 Fix team member access check (#35899) (#35905)
Backport #35899 by wxiaoguang

Fix #35499

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-09 03:44:53 +00:00
Giteabot
70ee6b9029 Fix conda null depend issue (#35900) (#35902)
Backport #35900 by Luohaothu

This fixes issue #35895

Co-authored-by: Luohao Wang <luohaothu@live.com>
2025-11-08 16:37:00 +00:00
wxiaoguang
e5b404ec53 Fix avatar upload error handling (#35887) (#35890)
Backport #35887
2025-11-07 11:25:34 +08:00
Giteabot
5842cd23a6 Contribution heatmap improvements (#35876) (#35880)
Backport #35876 by @silverwind

1. Set a fixed height on the element, preventing the content after the
element from shifting on page load. This uses CSS [container query
length
units](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries#container_query_length_units)
as I saw no other way because of the non-linear scaling of the element.
2. Move the "total-contributions" text into the existing vue slot,
eliminating the need for absolute positioning.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-06 08:51:49 +00:00
Giteabot
289bd9694b Remove padding override on .ui .sha.label (#35864) (#35873)
Backport #35864 by @silverwind

Since upgrading to v1.25, I noticed the SHA labels have slightly
different padding than before. I can't pinpoint exactly which change it
was. Fix it by removing the padding override on `.ui .sha.label` and
make the one on`.ui.label` (`2px 6px`) take effect which matches 1.24
rendering.

Before:

<img width="135" height="172" alt="image"
src="https://github.com/user-attachments/assets/2781a854-be08-4a11-bde0-d3699b2b7454"
/>

After:

<img width="139" height="162" alt="image"
src="https://github.com/user-attachments/assets/5c864fa3-c1f9-4452-ae58-5411dd445865"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-06 06:06:36 +00:00
Giteabot
154d7521a5 fix(api/repo/contents): set the dates to now when not specified by the caller (#35861) (#35874)
Backport #35861 by @divyun

Since 1.25.0, the dates get set to `2001-01-01T00:00:00Z`, when not
specified by the caller.

Fixes #35860

Co-authored-by: Divyun Raje Vaid <mail@divyun.com>
2025-11-06 13:08:06 +08:00
Giteabot
24189dcced Fix pull description code label background (#35865) (#35870)
Backport #35865 by @silverwind

Fix visual regression from https://github.com/go-gitea/gitea/pull/35567:

Before:

<img width="612" height="33" alt="image"
src="https://github.com/user-attachments/assets/aee4017c-b8b9-4ac2-9809-9d3eb3fda56c"
/>

After:

<img width="613" height="32" alt="image"
src="https://github.com/user-attachments/assets/ee6624da-b417-4e3b-8773-88c77c2cd672"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-05 17:29:06 +00:00
wxiaoguang
f84bf259ad Fix gogit ListEntriesRecursiveWithSize (#35862)
It needs to use full git path. Fix #35852.
2025-11-05 19:19:47 +02:00
Lunny Xiao
470b21056a Add changelog for 1.25.1 and add missing chagnelog for 1.24.x (#35838) 2025-11-04 11:17:59 -08:00
Zettat123
61011f1648 Add a doctor command to fix inconsistent run status (#35840) (#35845)
Backport #35840

#35783 fixes an actions rerun bug. Due to this bug, some runs may be
incorrectly marked as `StatusWaiting` even though all the jobs are in
done status. These runs cannot be run or cancelled. This PR adds a new
doctor command to fix the inconsistent run status.

```
gitea doctor check --run fix-actions-unfinished-run-status --fix
```
2025-11-04 11:16:36 -08:00
Giteabot
7ea9722c1d Make ACME email optional (#35849) (#35857)
Backport #35849 by @wxiaoguang

Fix a regression from #33668

Fix #35847

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-04 10:22:26 -08:00
Giteabot
297f63af42 Remove wrong code (#35846) (#35856)
Backport #35846 by @lunny

Follow #35821
Fix https://github.com/go-gitea/gitea/pull/35844#issuecomment-3483521045

The reviewed file numbers and progress have been set from backend so
that we don't need to update the numbers when clicking `load more`.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-04 20:04:08 +02:00
Lunny Xiao
6a55749359 Fix incorrect pull request counter (#35819) (#35841)
Fix #35781, #27472
Backport #35819 

The PR will not correct the wrong numbers automatically.

There is a cron task `check_repo_stats` which will be run when Gitea
start or midnight. It will correct the numbers.
2025-11-04 01:49:47 +00:00
Lunny Xiao
8116742e2d Fix viewed files number is not right if not all files loaded (#35821) (#35844)
Fix #35803
Backport #35821

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
2025-11-03 17:19:50 -08:00
Giteabot
0a9cbf3228 upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
Backport #35833 by @lunny

patch from
https://github.com/wneessen/go-mail/issues/504#issuecomment-3477890515.
Thanks to @wneessen

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-03 20:28:12 +00:00
Giteabot
74dfadb543 Fix circular spin animation direction (#35785) (#35823)
Backport #35785 by @lutinglt

Wait for the status icon to rotate clockwise instead of counterclockwise

before:
![GIF 2025-10-30
10-50-07](https://github.com/user-attachments/assets/3771b0bf-44e4-45a0-bde5-1b2b3dd8ba2a)

after:
![GIF 2025-10-30
10-50-43](https://github.com/user-attachments/assets/c45307fe-39a4-4e60-b48e-9d922c407565)

This can be merged to 1.25

Signed-off-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: lutinglt <lutinglt@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-03 09:37:01 +00:00
Giteabot
8ffc1fbfbf Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
Backport #35816 by @lunny

Revert gomail to the last work version to fix #35794

There is a problem between go mail v0.7.1 to prevent sending email work.
https://github.com/wneessen/go-mail/compare/v0.7.0...v0.7.1

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-03 09:08:13 +00:00
Giteabot
e95378329b Fix clone mixed bug (#35810) (#35822) 2025-11-02 10:20:27 -08:00
Giteabot
fddf6cd63f Fix cli "Before" handling (#35797) (#35808)
Backport #35797 by @wxiaoguang

Regression of #34973

Fix #35796

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-31 22:00:14 +01:00
Giteabot
d253e2055b Fix file extension on gogs.png (#35793) (#35799)
Backport #35793 by @silverwind

During https://github.com/go-gitea/gitea/issues/35790, it was noticed
that this PNG image had the wrong file extension. I also verified
`dingtalk.ico` and that one is actually an `.ico`.

Co-authored-by: silverwind <me@silverwind.io>
2025-10-31 03:33:27 +01:00
Giteabot
e194d89c74 Improve and fix markup code preview rendering (#35777) (#35787)
Backport #35777 by @silverwind

1. Add the color on the link to the referenced file, which is the more
likely thing the user wants to click
2. Use monospace font on the SHA
3. Tweak text colors
4. Change SHA link to go to the commit instead of the repo root with
commit filter set
5. Added the repo name to the file link text
6. Fix broken line numbering rendering

The only major difference to GitHub is now the missing line numbers.

Before:

<img width="286" height="162" alt="Screenshot 2025-10-29 at 19 09 59"
src="https://github.com/user-attachments/assets/f16b4eec-caf2-4c31-a2b5-ae5f41747d4b"
/>

After:

<img width="378" height="157" alt="image"
src="https://github.com/user-attachments/assets/0c91dfd3-0910-4b2d-a43b-8c87cfbb933e"
/>

For comparison, GitHub rendering:

<img width="286" height="177" alt="image"
src="https://github.com/user-attachments/assets/8a9a07b7-9153-4415-9d7a-5685853e472a"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-10-30 09:06:44 +00:00
Zettat123
04b6f90889 Fix actions rerun bug (#35783) (#35784)
Backport #35783

Fix #35780, fix #35782 

Rerunning a job or a run is only allowed when the job is done and the
run is done.

Related PR: #34970


98ff7d0773/routers/web/repo/actions/view.go (L239)

We don't need to check run status again in `rerunJob` because the run
status has been changed before `rerunJob`.

---

In fact, the bug described in the above issues will not occur on the
main branch. Because `getRunJobs` is called before updating the run.


98ff7d0773/routers/web/repo/actions/view.go (L425-L435)

So the run status that `rerunJob` checks is the old status.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-30 09:31:42 +01:00
Giteabot
65a37572f3 add pnpm to Snapcraft (#35778) (#35779)
Backport #35778 by techknowlogick

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
2025-10-29 19:35:18 +01:00
Giteabot
f85cd7aeb5 Fix actions schedule update issue (#35767) (#35774)
Backport #35767 by @Zettat123

Fix #34472

Add integration tests for actions schedule update.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-29 18:51:16 +01:00
Lunny Xiao
cf644d565d Update release notes for 1.25.0 (#35769)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-29 09:14:32 -07:00
Giteabot
88a8571b93 Update tab title when navigating file tree (#35757) (#35772)
Backport #35757 by bytedream
2025-10-29 14:04:19 +00:00
Giteabot
45a88e09af Fix "ref-issue" handling in markup (#35739) (#35771)
Backport #35739 by wxiaoguang

This is a follow up for #35662, and also fix #31181, help #30275, fix
#31161

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-29 19:03:02 +08:00
Lunny Xiao
6aa1a1e54d Upgrade go mail to 0.7.2 (#35748) (#35750)
backport #35748
2025-10-27 16:50:15 +00:00
Giteabot
18cc3160b5 Revert #18491, fix oauth2 client link account (#35745) (#35751)
Backport #35745 by @wxiaoguang

Fix #35744 by reverting #18491

* "OpenID" options don't mean "OAuth2Client" options
* "OAuth2(server)" options don't mean "OAuth2Client" options

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-26 15:26:40 -07:00
Lunny Xiao
123c8d2b81 Fix review request webhook bug (#35339) (#35596)
Frontport from #35339
2025-10-24 19:10:50 +00:00
wxiaoguang
b2f2f8528a Fix external render, make iframe render work (#35727, #35730) (#35731)
Backport #35727 and #35730

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-23 16:07:17 +08:00
Giteabot
0925089b5e Honor delete branch on merge repo setting when using merge API (#35488) (#35726)
Backport #35488 by @kemzeb

Fix #35463.

---------

Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-22 09:41:40 -07:00
Giteabot
c84d17b1bb Don't block site admin's operation if SECRET_KEY is lost (#35721) (#35724)
Backport #35721 by wxiaoguang

Related: #24573

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-22 05:02:47 +00:00
Giteabot
cb338a2ba1 fix attachment file size limit in server backend (#35519) (#35720)
Backport #35519 by @a1012112796

fix #35512

Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-21 18:01:59 +00:00
Giteabot
16f4f0d473 Make restricted users can access public repositories (#35693) (#35719)
Backport #35693 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-21 15:35:41 +00:00
Giteabot
387a4e72f7 Fix various trivial problems (#35714) (#35718)
Backport #35714 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-21 18:02:57 +08:00
Giteabot
ac6d38e4b7 Refactor legacy code (#35708) (#35716)
Backport #35708 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-21 05:47:37 +00:00
Giteabot
6df51d4ef5 Avoid emoji mismatch and allow to only enable chosen emojis (#35692) (#35705)
Backport #35692 by wxiaoguang

Fix #23635

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-20 08:29:02 +08:00
ChristopherHX
46f695ac65 Fix workflow run event status while rerunning a failed job (#35689) (#35703)
The event reported a completion status instead of requested, therefore
sent an email

Backport #35689
2025-10-19 10:49:20 -07:00
Giteabot
4af1d58c86 Fix various bugs (#35684) (#35696)
Backport #35684 by wxiaoguang
2025-10-19 02:26:03 +08:00
Giteabot
f71df88a6b Use LFS object size instead of blob size when viewing a LFS file (#35679) (#35680)
Backport #35679 by surya-purohit

shows the main LFS filesize instead of the pointer filesize when viewing
a file

Co-authored-by: Surya Purohit <suryaprakash.sharma@sourcefuse.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-16 22:41:24 +08:00
Lunny Xiao
18b178e63f Creating push comments before invoke pull request checking (#35647) (#35668)
Backport #35647 

This PR moved the creation of pushing comments before pull request
mergeable checking. So that when the pull request status changed, the
comments should have been created.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-15 17:42:52 +00:00
Lunny Xiao
1644b8743c Fix build (#35674)
backport #35656
2025-10-15 12:55:30 +00:00
wxiaoguang
53a2aaee35 Fix missing Close when error occurs and abused connection pool (#35658) (#35670)
Backport #35658
2025-10-15 09:56:53 +00:00
Giteabot
5ae9bb4df9 Fix a bug missed return (#35655) (#35667)
Backport #35655 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-15 07:40:31 +02:00
Giteabot
ae2e8c1f00 Always create Actions logs stepsContainer (#35654) (#35672)
Backport #35654 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-15 04:16:06 +00:00
techknowlogick
602af1499e bump archives&rar dep (#35637) (#35638) 2025-10-12 05:48:39 +02:00
Giteabot
f4512426a1 Fix code tag style problem and LFS view bug (#35628) (#35636)
Backport #35628 by lutinglt

Signed-off-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-11 20:25:03 +00:00
Giteabot
a3458c669a The status icon of the Action step is consistent with GitHub (#35618) (#35621)
Backport #35618 by @lutinglt

#35616
Before:
running:
<img width="45" height="34" alt="image"
src="https://github.com/user-attachments/assets/e2508f98-2f1f-4b7e-a80c-30b406f42531"
/>
waiting:
<img width="44" height="33" alt="image"
src="https://github.com/user-attachments/assets/e7c8164e-fdc3-4546-b088-31166544edb0"
/>

---
After:
running:
<img width="49" height="43" alt="image"
src="https://github.com/user-attachments/assets/b5a9b245-a995-458a-af23-d1723daa3692"
/>
waiting:
<img width="42" height="44" alt="image"
src="https://github.com/user-attachments/assets/ff72551e-cfb5-4665-af52-938ef0cf8f1c"
/>

`gitea-running.svg` is not an icon from the @ primer/octicon library,
extracted from the Github page. Github did not assign a clear class name
to this icon

Signed-off-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: lutinglt <lutinglt@users.noreply.github.com>
2025-10-10 12:12:21 -07:00
Giteabot
609d88f029 Fix inputing review comment will remove reviewer (#35591) (#35615)
Backport #35591 by @lunny

Fix #34617

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-10 08:28:44 +00:00
Giteabot
3c78598217 Mock external service in hcaptcha TestCaptcha (#35604) (#35614)
Backport #35604 by silverwind

The test calls out to a web service which may be down or unreachable as
seen in the linked issue. It's better for tests to not have such
external dependencies to make them absolutely stable.

Fixes: https://github.com/go-gitea/gitea/issues/35571

Co-authored-by: silverwind <me@silverwind.io>
2025-10-10 04:49:20 +00:00
Giteabot
b7bb0fa538 Fix diffpatch API endpoint (#35610) (#35613)
Backport #35610 by @surya-purohit

Updates the swagger documentation for the `diffpatch` API endpoint.

The request body is corrected from the outdated `UpdateFileOptions` to
the current `ApplyDiffPatchOptions` to match the code implementation.

Closes [issue#35602](https://github.com/go-gitea/gitea/issues/35602)

---------

Co-authored-by: Surya Purohit <suryaprakash.sharma@sourcefuse.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-10 03:28:40 +00:00
Giteabot
6de2151607 Fixing issue #35530: Password Leak in Log Messages (#35584) (#35609)
Backport #35584 by @shashank-netapp

# Summary
The Gitea codebase was logging `Elasticsearch` and `Meilisearch`
connection strings directly to log files without sanitizing them. Since
connection strings often contain credentials in the format
`protocol://username:password@host:port`, this resulted in passwords
being exposed in plain text in log output.

Fix:
- wrapped all instances of setting.Indexer.RepoConnStr and
setting.Indexer.IssueConnStr with the `util.SanitizeCredentialURLs()`
function before logging them.

Fixes: #35530

Co-authored-by: shashank-netapp <108022276+shashank-netapp@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-09 11:00:40 +02:00
Giteabot
a99761d466 Use inputs context when parsing workflows (#35590) (#35595)
Backport #35590 by @Zettat123

Depends on [gitea/act#143](https://gitea.com/gitea/act/pulls/143)

The [`inputs`
context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#inputs-context)
is used when parsing workflows so that `run-name` like `run-name: Deploy
to ${{ inputs.deploy_target }}` can be parsed correctly.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2025-10-06 22:28:17 +00:00
Giteabot
8d1c04bda4 Fix creating pull request failure when the target branch name is the same as some tag (#35552) (#35582)
Backport #35552 by @lunny

Use full reference name in the git command to avoid ambiguity.

Fix #35470

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-10-06 06:10:52 +02:00
Giteabot
aa57531aac fix: auto-expand and auto-scroll for actions logs (#35583) (#35586)
Backport #35583 by ita004

Co-authored-by: Shafi Ahmed <98274448+ita004@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-05 12:06:41 +08:00
Giteabot
006fe2a907 Add rebase push display wrong comments bug (#35560) (#35580)
Backport #35560 by @lunny

Fix #35518

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-10-04 10:28:32 +02:00
Giteabot
d94faf6d7e fix(webhook): prevent tag events from bypassing branch filters targets (#35567) (#35577)
Backport #35567 by Exgene

Co-authored-by: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2025-10-03 17:49:16 +00:00
Giteabot
6c8879b832 Fix markup init after issue comment editing (#35536) (#35537)
Backport #35536 by wxiaoguang

Fix #35533

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-09-26 07:11:21 +08:00
Lunny Xiao
94a6da3bc8 Add changelog for 1.25.0-rc0 (#35531) 2025-09-25 09:32:30 -07:00
349 changed files with 5686 additions and 2477 deletions

View File

@@ -25,6 +25,10 @@ insert_final_newline = false
[templates/user/auth/oidc_wellknown.tmpl]
indent_style = space
[templates/shared/actions/runner_badge_*.tmpl]
# editconfig lint requires these XML-like files to have charset defined, but the files don't have.
charset = unset
[Makefile]
indent_style = tab

View File

@@ -11,7 +11,7 @@ jobs:
if: github.repository == 'go-gitea/gitea'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -84,7 +84,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -101,7 +101,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -116,7 +116,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -145,7 +145,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -190,7 +190,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -39,7 +39,7 @@ jobs:
- "9000:9000"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -125,7 +125,7 @@ jobs:
- 10000:10000
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -178,7 +178,7 @@ jobs:
- "993:993"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -218,7 +218,7 @@ jobs:
- 10000:10000
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -16,7 +16,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -65,7 +65,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
@@ -107,7 +107,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -17,7 +17,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -21,7 +21,7 @@ jobs:
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true

View File

@@ -4,7 +4,394 @@ This changelog goes through 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.com).
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
## [1.25.2](https://github.com/go-gitea/gitea/releases/tag/1.25.2) - 2025-11-23
* SECURITY
* Upgrade golang.org/x/crypto to 0.45.0 (#35985) (#35988)
* Fix various permission & login related bugs (#36002) (#36004)
* ENHANCEMENTS
* Display source code downloads last for release attachments (#35897) (#35903)
* Change project default column icon to 'star' (#35967) (#35979)
* BUGFIXES
* Allow empty commit when merging pull request with squash style (#35989) (#36003)
* Fix container push tag overwriting (#35936) (#35954)
* Fix corrupted external render content (#35946) and upgrade golang.org/x packages (#35950)
* Limit reading bytes instead of ReadAll (#35928) (#35934)
* Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
* Fix team member access check (#35899) (#35905)
* Fix conda null depend issue (#35900) (#35902)
* Set the dates to now when not specified by the caller (#35861) (#35874)
* Fix gogit ListEntriesRecursiveWithSize (#35862)
* Misc CSS fixes (#35888) (#35981)
* Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
* Load jQuery as early as possible to support custom scripts (#35926) (#35929)
* Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
* Make OAuth2 issuer configurable (#35915) (#35916)
* Fix #35763: Add proper page title for project pages (#35773) (#35909)
* Fix avatar upload error handling (#35887) (#35890)
* Contribution heatmap improvements (#35876) (#35880)
* Remove padding override on `.ui .sha.label` (#35864) (#35873)
* Fix pull description code label background (#35865) (#35870)
## [1.25.1](https://github.com/go-gitea/gitea/releases/tag/v1.25.1) - 2025-11-03
* BUGFIXES
* Make ACME email optional (#35849) #35857
* Add a doctor command to fix inconsistent run status (#35840) (#35845)
* Remove wrong code (#35846)
* Fix viewed files number is not right if not all files loaded (#35821) (#35844)
* Fix incorrect pull request counter (#35819) (#35841)
* Upgrade go mail to 0.7.2 and fix the bug (#35833) (#35837)
* Revert gomail to v0.7.0 to fix sending mail failed (#35816) (#35824)
* Fix clone mixed bug (#35810) (#35822)
* Fix cli "Before" handling (#35797) (#35808)
* Improve and fix markup code preview rendering (#35777) (#35787)
* Fix actions rerun bug (#35783) (#35784)
* Fix actions schedule update issue (#35767) (#35774)
* Fix circular spin animation direction (#35785) (#35823)
* Fix file extension on gogs.png (#35793) (#35799)
* Add pnpm to Snapcraft (#35778)
## [1.25.0](https://github.com/go-gitea/gitea/releases/tag/v1.25.0) - 2025-10-30
* BREAKING
* Return 201 Created for CreateVariable API responses (#34517)
* Add label 'state' to metric 'gitea_users' (#34326)
* SECURITY
* Upgrade security public key (#34956)
* Also include all security fixes in 1.24.x after 1.25.0-rc0
* FEATURES
* Stream repo zip/tar.gz/bundle achives by default (#35487)
* Use configurable remote name for git commands (#35172)
* Send email on Workflow Run Success/Failure (#34982)
* Refactor OpenIDConnect to support SSH/FullName sync (#34978)
* Refactor repo contents API and add "contents-ext" API (#34822)
* Add support for 3D/CAD file formats preview (#34794)
* Improve instance wide ssh commit signing (#34341)
* Edit file workflow for creating a fork and proposing changes (#34240)
* Follow file symlinks in the UI to their target (#28835)
* Allow renaming/moving binary/LFS files in the UI (#34350)
* PERFORMANCE
* Improve the performance when detecting the file editable (#34653)
* ENHANCEMENTS
* Enable more markdown paste features in textarea editor (#35494)
* Don't store repo archives on `gitea dump` (#35467)
* Always return the relevant status information, even if no status exists. (#35335)
* Add start time on perf trace because it seems some steps haven't been recorded. (#35282)
* Remove deprecated auth sources (#35272)
* When sorting issues by nearest due date, issues without due date should be sorted ascending (#35267)
* Disable field count validation of CSV viewer (#35228)
* Add `has_code` to repository REST API (#35214)
* Display pull request in merged commit view (#35202)
* Support Basic Authentication for archive downloads (#35087)
* Add hover background to table rows in user and repo admin page (#35072)
* Partially refresh notifications list (#35010)
* Also display "recently pushed branch" alert on PR view (#35001)
* Refactor time tracker UI (#34983)
* Improve CLI commands (#34973)
* Improve project & label color picker and image scroll (#34971)
* Improve NuGet API Parity (#21291) (#34940)
* Support getting last commit message using contents-ext API (#34904)
* Adds title on branch commit counts (#34869)
* Add "Cancel workflow run" button to Actions list page (#34817)
* Improve img lazy loading (#34804)
* Forks repository list page follow other repositories page (#34784)
* Add ff_only parameter to POST /repos/{owner}/{repo}/merge-upstream (#34770)
* Rework delete org and rename org UI (#34762)
* Improve nuget/rubygems package registries (#34741)
* Add repo file tree item link behavior (#34730)
* Add issue delete notifier (#34592)
* Improve Actions list (#34530)
* Add a default tab on repo header when migrating (#34503)
* Add post-installation redirect based on admin account status (#34493)
* Trigger 'unlabeled' event when label is Deleted from PR (#34316)
* Support annotated tags when using create release API (#31840)
* Use lfs label for lfs file rather than a long description (#34363)
* Add "View workflow file" to Actions list page (#34538)
* Move organization's visibility change to danger zone. (#34814)
* Don't block site admin's operation if SECRET_KEY is lost (#35721)
* Make restricted users can access public repositories (#35693)
* The status icon of the Action step is consistent with GitHub (#35618) #35621
* BUGFIXES
* Update tab title when navigating file tree (#35757) #35772
* Fix "ref-issue" handling in markup (#35739) #35771
* Fix webhook to prevent tag events from bypassing branch filters targets (#35567) #35577
* Fix markup init after issue comment editing (#35536) #35537
* Fix creating pull request failure when the target branch name is the same as some tag (#35552) #35582
* Fix auto-expand and auto-scroll for actions logs (#35570) (#35583) #35586
* Use inputs context when parsing workflows (#35590) #35595
* Fix diffpatch API endpoint (#35610) #35613
* Creating push comments before invoke pull request checking (#35647) #35668
* Fix missing Close when error occurs and abused connection pool (#35658) #35670
* Fix build (#35674)
* Use LFS object size instead of blob size when viewing a LFS file (#35679)
* Fix workflow run event status while rerunning a failed job (#35689)
* Avoid emoji mismatch and allow to only enable chosen emojis (#35692)
* Refactor legacy code, fix LFS auth bypass, fix symlink bypass (#35708)
* Fix various trivial problems (#35714)
* Fix attachment file size limit in server backend (#35519)
* Honor delete branch on merge repo setting when using merge API (#35488)
* Fix external render, make iframe render work (#35727, #35730)
* Upgrade go mail to 0.7.2 (#35748)
* Revert #18491, fix oauth2 client link account (#35745)
* Fix different behavior in status check pattern matching with double stars (#35474)
* Fix overflow in notifications list (#35446)
* Fix package link setting can only list limited repositories (#35394)
* Extend comment treepath length (#35389)
* Fix font-size in inline code comment preview (#35209)
* Move git config/remote to gitrepo package and add global lock to resolve possible conflict when updating repository git config file (#35151)
* Change some columns from text to longtext and fix column wrong type caused by xorm (#35141)
* Redirect to a presigned URL of HEAD for HEAD requests (#35088)
* Fix git commit committer parsing and add some tests (#35007)
* Fix OCI manifest parser (#34797)
* Refactor FindOrgOptions to use enum instead of bool, fix membership visibility (#34629)
* Fix notification count positioning for variable-width elements (#34597)
* Keeping consistent between UI and API about combined commit status state and fix some bugs (#34562)
* Fix possible panic (#34508)
* Fix autofocus behavior (#34397)
* Fix Actions API (#35204)
* Fix ListWorkflowRuns OpenAPI response model. (#35026)
* Small fix in Pull Requests page (#34612)
* Fix http auth header parsing (#34936)
* Fix modal + form abuse (#34921)
* Fix PR toggle WIP (#34920)
* Fix log fmt (#34810)
* Replace stopwatch toggle with explicit start/stop actions (#34818)
* Fix some package registry problems (#34759)
* Fix RPM package download routing & missing package version count (#34909)
* Fix repo search input height (#34330)
* Fix "The sidebar of the repository file list does not have a fixed height #34298" (#34321)
* Fix minor typos in two files #HSFDPMUW (#34944)
* Fix actions skipped commit status indicator (#34507)
* Fix job status aggregation logic (#35000)
* Fix broken OneDev migration caused by various REST API changes in OneDev 7.8.0 and later (#35216)
* Fix typo in oauth2_full_name_claim_name string (#35199)
* Fix typo in locale_en-US.ini (#35196)
* API
* Exposing TimeEstimate field in the API (#35475)
* UpdateBranch API supports renaming a branch (#35374)
* Add `owner` and `parent` fields clarification to docs (#35023)
* Improve OAuth2 provider (correct Issuer, respect ENABLED) (#34966)
* Add a `login`/`login-name`/`username` disambiguation to affected endpoint parameters and response/request models (#34901)
* Do not mutate incoming options to SearchRepositoryByName (#34553)
* Do not mutate incoming options to RenderUserSearch and SearchUsers (#34544)
* Export repo's manual merge settings (#34502)
* Add date range filtering to commit retrieval endpoints (#34497)
* Add endpoint deleting workflow run (#34337)
* Add workflow_run api + webhook (#33964)
* REFACTOR
* Move updateref and removeref to gitrepo and remove unnecessary open repository (#35511)
* Remove unused param `doer` (#34545)
* Split GetLatestCommitStatus as two functions (#34535)
* Use gitrepo.SetDefaultBranch when set default branch of wiki repository (#33911)
* Refactor editor (#34780)
* Refactor packages (#34777)
* Refactor container package (#34877)
* Refactor "change file" API (#34855)
* Rename pull request GetGitRefName to GetGitHeadRefName to prepare introducing GetGitMergeRefName (#35093)
* Move git command to git/gitcmd (#35483)
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35428)
* Support Node.js 22.6 with type stripping (#35427)
* Migrate tools and configs to typescript, require node.js >= 22.18.0 (#35421)
* Check user and repo for redirects when using git via SSH transport (#35416)
* Remove the duplicated function GetTags (#35375)
* Refactor to use reflect.TypeFor (#35370)
* Deleting branch could delete broken branch which has database record but git branch is missing (#35360)
* Exit with success when already up to date (#35312)
* Split admin config settings templates to make it maintain easier (#35294)
* A small refactor to use context in the service layer (#35179)
* Refactor and update mail templates (#35150)
* Use db.WithTx/WithTx2 instead of TxContext when possible (#35130)
* Align `issue-title-buttons` with `list-header` (#35018)
* Add Notifications section in User Settings (#35008)
* Tweak placement of diff file menu (#34999)
* Refactor mail template and support preview (#34990)
* Rerun job only when run is done (#34970)
* Merge index.js (#34963)
* Refactor "delete-button" to "link-action" (#34962)
* Refactor webhook and fix feishu/lark secret (#34961)
* Exclude devtest.ts from tailwindcss (#34935)
* Refactor head navbar icons (#34922)
* Improve html escape (#34911)
* Improve tags list page (#34898)
* Improve `labels-list` rendering (#34846)
* Remove unused variable HUGO_VERSION (#34840)
* Correct migration tab name (#34826)
* Refactor template helper (#34819)
* Use `shallowRef` instead of `ref` in `.vue` files where possible (#34813)
* Use standalone function to update repository cols (#34811)
* Refactor wiki (#34805)
* Remove unnecessary duplicate code (#34733)
* Refactor embedded assets and drop unnecessary dependencies (#34692)
* Update x/crypto package and make builtin SSH use default parameters (#34667)
* Add `--color-logo`, matching the logo's primary color (#34639)
* Add openssh-keygen to rootless image (#34625)
* Replace update repository function in some places (#34566)
* Change "rejected" to "changes requested" in 3rd party PR review notification (#34481)
* Remove legacy template helper functions (#34426)
* Use run-name and evaluate workflow variables (#34301)
* Move HasWiki to repository service package (#33912)
* Move some functions from package git to gitrepo (#33910)
* TESTING
* Add webhook test for push event (#34442)
* Add a webhook push test for dev branch (#34421)
* Add migrations tests (#34456) (#34498)
* STYLE
* Enforce explanation for necessary nolints and fix bugs (#34883)
* Fix remaining issues after `gopls modernize` formatting (#34771)
* Update gofumpt, add go.mod ignore directive (#35434)
* Enforce nolint scope (#34851)
* Enable gocritic `equalFold` and fix issues (#34952)
* Run `gopls modernize` on codebase (#34751)
* Upgrade `gopls` to v0.19.0, add `make fix` (#34772)
* BUILD
* bump archives&rar dep (#35637) #35638
* Use github.com/mholt/archives replace github.com/mholt/archiver (#35390)
* Update JS and PY dependencies (#35444)
* Upgrade devcontainer go version to 1.24.6 (#35298)
* Upgrade golang to 1.25.1 and add descriptions for the swagger structs' fields (#35418)
* Update JS and PY deps (#35191)
* Update JS and PY dependencies (#34391)
* Update go tool dependencies (#34845)
* Update `uint8-to-base64`, remove type stub (#34844)
* Switch to `@resvg/resvg-wasm` for `generate-images` (#35415)
* Switch to pnpm (#35274)
* Update chroma to v2.20.0 (#35220)
* Migrate to urfave v3 (#34510)
* Update JS deps, regenerate SVGs (#34640)
* Upgrade dependencies (#35384)
* Bump `@github/relative-time-element` to v4.4.8 (#34413)
* Update JS dependencies (#34951)
* Upgrade orgmode to v1.8.0 (#34721)
* Raise minimum Node.js version to 20, test on 24 (#34713)
* Update JS deps (#34701)
* Upgrade htmx to 2.0.6 (#34887)
* Update eslint to v9 (#35485)
* Update js dependencies (#35429)
* Clean up npm dependencies (#35508)
* Clean up npm dependencies (#35484)
* Bump setup-node to v5 (#35448)
* MISC
* Add gitignore rules to exclude LLM instruction files (#35076)
* Gitignore: Visual Studio settings folder (#34375)
* Improve language in en-US locale strings (#35124)
* Fixed all grammatical errors in locale_en-US.ini (#35053)
* Docs/fix typo and grammar in CONTRIBUTING.md (#35024)
* Improve english grammar and readability in locale_en-US.ini (#35017)
## [1.24.7](https://github.com/go-gitea/gitea/releases/tag/v1.24.7) - 2025-10-24
* SECURITY
* Refactor legacy code (#35708) (#35713)
* Fixing issue #35530: Password Leak in Log Messages (#35584) (#35665)
* Fix a bug missed return (#35655) (#35671)
* BUGFIXES
* Fix inputing review comment will remove reviewer (#35591) (#35664)
* TESTING
* Mock external service in hcaptcha TestCaptcha (#35604) (#35663)
* Fix build (#35669)
## [1.24.6](https://github.com/go-gitea/gitea/releases/tag/v1.24.6) - 2025-09-10
* SECURITY
* Upgrade xz to v0.5.15 (#35385)
* BUGFIXES
* Fix a compare page 404 bug when the pull request disabled (#35441) (#35453)
* Fix bug when issue disabled, pull request number in the commit message cannot be redirected (#35420) (#35442)
* Add author.name field to Swift Package Registry API response (#35410) (#35431)
* Remove usernames when empty in discord webhook (#35412) (#35417)
* Allow foreachref parser to grow its buffer (#35365) (#35376)
* Allow deleting comment with content via API like web did (#35346) (#35354)
* Fix atom/rss mixed error (#35345) (#35347)
* Fix review request webhook bug (#35339)
* Remove duplicate html IDs (#35210) (#35325)
* Fix LFS range size header response (#35277) (#35293)
* Fix GitHub release assets URL validation (#35287) (#35290)
* Fix token lifetime, closes #35230 (#35271) (#35281)
* Fix push commits comments when changing the pull request target branch (#35386) (#35443)
## [1.24.5](https://github.com/go-gitea/gitea/releases/tag/v1.24.5) - 2025-08-12
* BUGFIXES
* Fix a bug where lfs gc never worked. (#35198) (#35255)
* Reload issue when sending webhook to make num comments is right. (#35243) (#35248)
* Fix bug when review pull request commits (#35192) (#35246)
* MISC
* Vertically center "Show Resolved" (#35211) (#35218)
## [1.24.4](https://github.com/go-gitea/gitea/releases/tag/v1.24.4) - 2025-08-03
* BUGFIXES
* Fix various bugs (1.24) (#35186)
* Fix migrate input box bug (#35166) (#35171)
* Only hide dropzone when no files have been uploaded (#35156) (#35167)
* Fix review comment/dimiss comment x reference can be refereced back (#35094) (#35099)
* Fix submodule nil check (#35096) (#35098)
* MISC
* Don't use full-file highlight when there is a git diff textconv (#35114) (#35119)
* Increase gap on latest commit (#35104) (#35113)
## [1.24.3](https://github.com/go-gitea/gitea/releases/tag/v1.24.3) - 2025-07-15
* BUGFIXES
* Fix form property assignment edge case (#35073) (#35078)
* Improve submodule relative path handling (#35056) (#35075)
* Fix incorrect comment diff hunk parsing, fix github asset ID nil panic (#35046) (#35055)
* Fix updating user visibility (#35036) (#35044)
* Support base64-encoded agit push options (#35037) (#35041)
* Make submodule link work with relative path (#35034) (#35038)
* Fix bug when displaying git user avatar in commits list (#35006)
* Fix API response for swagger spec (#35029)
* Start automerge check again after the conflict check and the schedule (#34988) (#35002)
* Fix the response format for actions/workflows (#35009) (#35016)
* Fix repo settings and protocol log problems (#35012) (#35013)
* Fix project images scroll (#34971) (#34972)
* Mark old reviews as stale on agit pr updates (#34933) (#34965)
* Fix git graph page (#34948) (#34949)
* Don't send trigger for a pending review's comment create/update/delete (#34928) (#34939)
* Fix some log and UI problems (#34863) (#34868)
* Fix archive API (#34853) (#34857)
* Ignore force pushes for changed files in a PR review (#34837) (#34843)
* Fix SSH LFS timeout (#34838) (#34842)
* Fix team permissions (#34827) (#34836)
* Fix job status aggregation logic (#34823) (#34835)
* Fix issue filter (#34914) (#34915)
* Fix typo in pull request merge warning message text (#34899) (#34903)
* Support the open-icon of folder (#34168) (#34896)
* Optimize flex layout of release attachment area (#34885) (#34886)
* Fix the issue of abnormal interface when there is no issue-item on the project page (#34791) (#34880)
* Skip updating timestamp when sync branch (#34875)
* Fix required contexts and commit status matching bug (#34815) (#34829)
## [1.24.2](https://github.com/go-gitea/gitea/releases/tag/v1.24.2) - 2025-06-20
* BUGFIXES
* Fix container range bug (#34795) (#34796)
* Upgrade chi to v5.2.2 (#34798) (#34799)
* BUILD
* Bump poetry feature to new url for dev container (#34787) (#34790)
## [1.24.1](https://github.com/go-gitea/gitea/releases/tag/v1.24.1) - 2025-06-18
* ENHANCEMENTS
* Improve alignment of commit status icon on commit page (#34750) (#34757)
* Support title and body query parameters for new PRs (#34537) (#34752)
* BUGFIXES
* When using rules to delete packages, remove unclean bugs (#34632) (#34761)
* Fix ghost user in feeds when pushing in an actions, it should be gitea-actions (#34703) (#34756)
* Prevent double markdown link brackets when pasting URL (#34745) (#34748)
* Prevent duplicate form submissions when creating forks (#34714) (#34735)
* Fix markdown wrap (#34697) (#34702)
* Fix pull requests API convert panic when head repository is deleted. (#34685) (#34687)
* Fix commit message rendering and some UI problems (#34680) (#34683)
* Fix container range bug (#34725) (#34732)
* Fix incorrect cli default values (#34765) (#34766)
* Fix dropdown filter (#34708) (#34711)
* Hide href attribute of a tag if there is no target_url (#34556) (#34684)
* Fix tag target (#34781) #34783
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/v1.24.0) - 2025-05-26
* BREAKING
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
@@ -374,7 +761,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Bump x/net (#32896) (#32900)
* Only activity tab needs heatmap data loading (#34652)
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/v1.23.8) - 2025-05-11
* SECURITY
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
@@ -401,7 +788,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Bump go version in go.mod (#34160)
* remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/1.23.7) - 2025-04-07
## [1.23.7](https://github.com/go-gitea/gitea/releases/tag/v1.23.7) - 2025-04-07
* Enhancements
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
@@ -499,7 +886,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* BUGFIXES
* Fix a bug caused by status webhook template #33512
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/1.23.2) - 2025-02-04
## [1.23.2](https://github.com/go-gitea/gitea/releases/tag/v1.23.2) - 2025-02-04
* BREAKING
* Add tests for webhook and fix some webhook bugs (#33396) (#33442)
@@ -3029,7 +3416,7 @@ Key highlights of this release encompass significant changes categorized under `
* Improve decryption failure message (#24573) (#24575)
* Makefile: Use portable !, not GNUish -not, with find(1). (#24565) (#24572)
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/1.19.3) - 2023-05-03
## [1.19.3](https://github.com/go-gitea/gitea/releases/tag/v1.19.3) - 2023-05-03
* SECURITY
* Use golang 1.20.4 to fix CVE-2023-24539, CVE-2023-24540, and CVE-2023-29400
@@ -3042,7 +3429,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix incorrect CurrentUser check for docker rootless (#24435)
* Getting the tag list does not require being signed in (#24413) (#24416)
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26
## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/v1.19.2) - 2023-04-26
* SECURITY
* Require repo scope for PATs for private repos and basic authentication (#24362) (#24364)
@@ -3541,7 +3928,7 @@ Key highlights of this release encompass significant changes categorized under `
* Display attachments of review comment when comment content is blank (#23035) (#23046)
* Return empty url for submodule tree entries (#23043) (#23048)
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/v1.18.4) - 2023-02-20
* SECURITY
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
@@ -3968,7 +4355,7 @@ Key highlights of this release encompass significant changes categorized under `
* Fix the mode of custom dir to 0700 in docker-rootless (#20861) (#20867)
* Fix UI mis-align for PR commit history (#20845) (#20859)
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17
## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/v1.17.1) - 2022-08-17
* SECURITY
* Correctly escape within tribute.js (#20831) (#20832)

View File

@@ -35,7 +35,7 @@ SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@717e3cb29becaaf0
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.8
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.20.0
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.20.0

File diff suppressed because one or more lines are too long

View File

@@ -121,6 +121,12 @@ func globalBool(c *cli.Command, name string) bool {
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) {
return func(ctx context.Context, c *cli.Command) (context.Context, error) {
if setting.InstallLock {
// During config loading, there might also be logs (for example: deprecation warnings).
// It must make sure that console logger is set up before config is loaded.
log.Error("Config is loaded before console logger is setup, it will cause bugs. Please fix it.")
return nil, errors.New("console logger must be setup before config is loaded")
}
level := defaultLevel
if globalBool(c, "quiet") {
level = log.FATAL

View File

@@ -19,7 +19,7 @@ import (
var CmdKeys = &cli.Command{
Name: "keys",
Usage: "(internal) Should only be called by SSH server",
Hidden: true, // internal commands shouldn't not be visible
Hidden: true, // internal commands shouldn't be visible
Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys,

View File

@@ -50,11 +50,15 @@ DEFAULT CONFIGURATION:
func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) {
originBefore := originCmd.Before
originCmd.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) {
prepareWorkPathAndCustomConf(cmd)
originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) {
ctx = ctxOrig
if originBefore != nil {
return originBefore(ctx, cmd)
ctx, err = originBefore(ctx, cmd)
if err != nil {
return ctx, err
}
}
prepareWorkPathAndCustomConf(cmd)
return ctx, nil
}
}

View File

@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
@@ -28,11 +29,11 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
}
func newTestApp(testCmdAction cli.ActionFunc) *cli.Command {
func newTestApp(testCmd cli.Command) *cli.Command {
app := NewMainApp(AppVersion{})
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithGlobalFlags(testCmd)
app.Commands = append(app.Commands, testCmd)
testCmd.Name = util.IfZero(testCmd.Name, "test-cmd")
prepareSubcommandWithGlobalFlags(&testCmd)
app.Commands = append(app.Commands, &testCmd)
app.DefaultCommand = testCmd.Name
return app
}
@@ -156,9 +157,11 @@ func TestCliCmd(t *testing.T) {
for _, c := range cases {
t.Run(c.cmd, func(t *testing.T) {
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
app := newTestApp(cli.Command{
Action: func(ctx context.Context, cmd *cli.Command) error {
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil
},
})
for k, v := range c.env {
t.Setenv(k, v)
@@ -173,31 +176,54 @@ func TestCliCmd(t *testing.T) {
}
func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") })
app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }})
r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }})
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil })
app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }})
r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Empty(t, r.Stdout)
assert.Empty(t, r.Stderr)
}
func TestCliCmdBefore(t *testing.T) {
ctxNew := context.WithValue(context.Background(), any("key"), "value")
configValues := map[string]string{}
setting.CustomConf = "/tmp/any.ini"
var actionCtx context.Context
app := newTestApp(cli.Command{
Before: func(context.Context, *cli.Command) (context.Context, error) {
configValues["before"] = setting.CustomConf
return ctxNew, nil
},
Action: func(ctx context.Context, cmd *cli.Command) error {
configValues["action"] = setting.CustomConf
actionCtx = ctx
return nil
},
})
_, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd")
assert.NoError(t, err)
assert.Equal(t, ctxNew, actionCtx)
assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config")
assert.Equal(t, "/dev/null", configValues["action"])
}

View File

@@ -13,13 +13,12 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/json"
@@ -32,7 +31,6 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli/v3"
)
@@ -133,27 +131,6 @@ func getAccessMode(verb, lfsVerb string) perm.AccessMode {
return perm.AccessModeNone
}
func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
now := time.Now()
claims := lfs.Claims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
NotBefore: jwt.NewNumericDate(now),
},
RepoID: results.RepoID,
Op: lfsVerb,
UserID: results.UserID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
if err != nil {
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
}
return "Bearer " + tokenString, nil
}
func runServ(ctx context.Context, c *cli.Command) error {
// FIXME: This needs to internationalised
setup(ctx, c.Bool("debug"))
@@ -230,7 +207,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
username := repoPathFields[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
if !repo.IsValidSSHAccessRepoName(reponame) {
if !repo_model.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@@ -276,14 +253,16 @@ func runServ(ctx context.Context, c *cli.Command) error {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(results.OwnerName + "/" + results.RepoName + ".git")
// because the original repoPath maybe redirected, we need to use the returned actual repository information
if results.IsWiki {
repoPath = repo_model.RelativeWikiPath(results.OwnerName, results.RepoName)
} else {
repoPath = repo_model.RelativePath(results.OwnerName, results.RepoName)
}
// LFS SSH protocol
if verb == git.CmdVerbLfsTransfer {
token, err := getLFSAuthToken(ctx, lfsVerb, results)
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
if err != nil {
return err
}
@@ -294,7 +273,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
if verb == git.CmdVerbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := getLFSAuthToken(ctx, lfsVerb, results)
token, err := lfs.GetLFSAuthTokenWithBearer(lfs.AuthTokenOptions{Op: lfsVerb, UserID: results.UserID, RepoID: results.RepoID})
if err != nil {
return err
}

View File

@@ -567,6 +567,11 @@ ENABLED = true
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
;;
;; The "issuer" claim identifies the principal that issued the JWT.
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
;JWT_CLAIM_ISSUER =
;;
;; Lifetime of an OAuth2 access token in seconds
;ACCESS_TOKEN_EXPIRATION_TIME = 3600
;;
@@ -1343,6 +1348,10 @@ LEVEL = Info
;; Dont mistake it for Reactions.
;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs
;;
;; Comma separated list of enabled emojis, for example: smile, thumbsup, thumbsdown
;; Leave it empty to enable all emojis.
;ENABLED_EMOJIS =
;;
;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used.
;DEFAULT_SHOW_FULL_NAME = false
;;
@@ -2536,7 +2545,19 @@ LEVEL = Info
;; * sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in [markup.sanitizer.*] .
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
;RENDER_CONTENT_MODE=sanitized
;RENDER_CONTENT_MODE = sanitized
;; The sandbox applied to the iframe and Content-Security-Policy header when RENDER_CONTENT_MODE is `iframe`.
;; It defaults to a safe set of "allow-*" restrictions (space separated).
;; You can also set it by your requirements or use "disabled" to disable the sandbox completely.
;; When set it, make sure there is no security risk:
;; * PDF-only content: generally safe to use "disabled", and it needs to be "disabled" because PDF only renders with no sandbox.
;; * HTML content with JS: if the "RENDER_COMMAND" can guarantee there is no XSS, then it is safe, otherwise, you need to fine tune the "allow-*" restrictions.
;RENDER_CONTENT_SANDBOX =
;; Whether post-process the rendered HTML content, including:
;; resolve relative links and image sources, recognizing issue/commit references, escaping invisible characters,
;; mentioning users, rendering permlink code blocks, replacing emoji shorthands, etc.
;; By default, this is true when RENDER_CONTENT_MODE is `sanitized`, otherwise false.
;NEED_POST_PROCESS = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

42
go.mod
View File

@@ -1,6 +1,8 @@
module code.gitea.io/gitea
go 1.25.1
go 1.25.0
toolchain go1.25.5
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
// But some CAs use negative serial number, just relax the check. related:
@@ -35,7 +37,7 @@ require (
github.com/bohde/codel v0.2.0
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.24.0
github.com/charmbracelet/git-lfs-transfer v0.2.0
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21
github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0
@@ -56,7 +58,7 @@ require (
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.2
github.com/go-git/go-git/v5 v5.16.3
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.3
@@ -84,7 +86,7 @@ require (
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.32
github.com/meilisearch/meilisearch-go v0.33.2
github.com/mholt/archives v0.1.3
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726
github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.9.3
github.com/minio/minio-go/v7 v7.0.95
@@ -109,20 +111,20 @@ require (
github.com/ulikunitz/xz v0.5.15
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
github.com/urfave/cli/v3 v3.4.1
github.com/wneessen/go-mail v0.6.2
github.com/wneessen/go-mail v0.7.2
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.142.4
golang.org/x/crypto v0.41.0
golang.org/x/crypto v0.45.0
golang.org/x/image v0.30.0
golang.org/x/net v0.43.0
golang.org/x/net v0.47.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
golang.org/x/sync v0.18.0
golang.org/x/sys v0.38.0
golang.org/x/text v0.31.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
gopkg.in/ini.v1 v1.67.0
@@ -142,7 +144,7 @@ require (
github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect
github.com/STARRY-S/zip v0.2.1 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
@@ -172,7 +174,7 @@ require (
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
@@ -233,14 +235,14 @@ require (
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minlz v1.0.0 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.1.0 // indirect
@@ -259,7 +261,8 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/tinylib/msgp v1.4.0 // indirect
github.com/unknwon/com v1.0.1 // indirect
@@ -278,9 +281,9 @@ require (
go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -295,10 +298,7 @@ replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-202
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078

80
go.sum
View File

@@ -31,10 +31,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitea.com/gitea/act v0.261.6 h1:CjZwKOyejonNFDmsXOw3wGm5Vet573hHM6VMLsxtvPY=
gitea.com/gitea/act v0.261.6/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 h1:ohdxegvslDEllZmRNDqpKun6L4Oq81jNdEDtGgHEV2c=
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso=
@@ -93,8 +91,8 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06
github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
github.com/RoaringBitmap/roaring/v2 v2.10.0 h1:HbJ8Cs71lfCJyvmSptxeMX2PtvOC8yonlU0GQcy2Ak0=
github.com/RoaringBitmap/roaring/v2 v2.10.0/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 h1:tgjwQrDH5m6jIYB7kac5IQZmfUzQNseac/e3H4VoCNE=
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0/go.mod h1:1HmmMEVsr+0R1QWahSeMJkjSkq6CYAZu1aIbYSpfJ4o=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
@@ -193,8 +191,8 @@ github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bohde/codel v0.2.0 h1:fzF7ibgKmCfQbOzQCblmQcwzDRmV7WO7VMLm/hDvD3E=
@@ -219,6 +217,8 @@ github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 h1:2d64+4Jek9vjYwhY93AjbleiVH+AeWvPwPmDi1mfKFQ=
github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21/go.mod h1:fNlYtCHWTRC8MofQERZkVUNUWaOvZeTBqHn/amSbKZI=
github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ=
github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
@@ -339,8 +339,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
@@ -572,8 +572,8 @@ github.com/meilisearch/meilisearch-go v0.33.2 h1:YgsQSLYhAkRN2ias6I1KNRTjdYCN5w2
github.com/meilisearch/meilisearch-go v0.33.2/go.mod h1:6eOPcQ+OAuwXvnONlfSgfgvr7TIAWM/6OdhcVHg8cF0=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726 h1:WVjGWXBLI1Ggm2kHzNraCGgxFhLoK6gdpPSizCdxnx0=
github.com/mholt/archives v0.1.5-0.20251009205813-e30ac6010726/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
@@ -588,8 +588,8 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -610,8 +610,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0=
github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48=
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -714,9 +714,11 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -729,6 +731,7 @@ github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -765,8 +768,8 @@ github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZ
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/wneessen/go-mail v0.6.2 h1:c6V7c8D2mz868z9WJ+8zDKtUyLfZ1++uAZmo2GRFji8=
github.com/wneessen/go-mail v0.6.2/go.mod h1:L/PYjPK3/2ZlNb2/FjEBIn9n1rUWjW+Toy531oVmeb4=
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@@ -837,9 +840,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
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=
@@ -876,8 +878,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -906,8 +908,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -930,9 +932,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -974,9 +975,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
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=
@@ -987,9 +987,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
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=
@@ -1003,9 +1002,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@@ -1041,8 +1039,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -13,6 +13,8 @@ func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
FixtureFiles: []string{
"action_runner_token.yml",
"action_run.yml",
"repository.yml",
},
})
}

View File

@@ -184,6 +184,7 @@ func (run *ActionRun) IsSchedule() bool {
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID).
NoAutoTime().
Cols("num_action_runs", "num_closed_action_runs").
SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}),

View File

@@ -0,0 +1,35 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
)
func TestUpdateRepoRunsNumbers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// update the number to a wrong one, the original is 3
_, err := db.GetEngine(t.Context()).ID(4).Cols("num_closed_action_runs").Update(&repo_model.Repository{
NumClosedActionRuns: 2,
})
assert.NoError(t, err)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 4, repo.NumActionRuns)
assert.Equal(t, 2, repo.NumClosedActionRuns)
// now update will correct them, only num_actionr_runs and num_closed_action_runs should be updated
err = updateRepoRunsNumbers(t.Context(), repo)
assert.NoError(t, err)
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
assert.Equal(t, 5, repo.NumActionRuns)
assert.Equal(t, 3, repo.NumClosedActionRuns)
}

View File

@@ -386,7 +386,7 @@ func SetNotificationStatus(ctx context.Context, notificationID int64, user *user
notification.Status = status
_, err = db.GetEngine(ctx).ID(notificationID).Update(notification)
_, err = db.GetEngine(ctx).ID(notificationID).Cols("status").Update(notification)
return notification, err
}

View File

@@ -11,6 +11,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
@@ -123,17 +124,17 @@ func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) {
// decrypt credentials
if opts.CloneAddrEncrypted != "" {
if opts.CloneAddr, err = secret.DecryptSecret(setting.SecretKey, opts.CloneAddrEncrypted); err != nil {
return nil, err
log.Error("Unable to decrypt CloneAddr, maybe SECRET_KEY is wrong: %v", err)
}
}
if opts.AuthPasswordEncrypted != "" {
if opts.AuthPassword, err = secret.DecryptSecret(setting.SecretKey, opts.AuthPasswordEncrypted); err != nil {
return nil, err
log.Error("Unable to decrypt AuthPassword, maybe SECRET_KEY is wrong: %v", err)
}
}
if opts.AuthTokenEncrypted != "" {
if opts.AuthToken, err = secret.DecryptSecret(setting.SecretKey, opts.AuthTokenEncrypted); err != nil {
return nil, err
log.Error("Unable to decrypt AuthToken, maybe SECRET_KEY is wrong: %v", err)
}
}

View File

@@ -78,7 +78,7 @@ func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature st
}
key.Verified = true
if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil {
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {
return "", err
}

View File

@@ -67,13 +67,6 @@ func (key *PublicKey) OmitEmail() string {
return strings.Join(strings.Split(key.Content, " ")[:2], " ")
}
// AuthorizedString returns formatted public key string for authorized_keys file.
//
// TODO: Consider dropping this function
func (key *PublicKey) AuthorizedString() string {
return AuthorizedStringForKey(key)
}
func addKey(ctx context.Context, key *PublicKey) (err error) {
if len(key.Fingerprint) == 0 {
key.Fingerprint, err = CalcFingerprint(key.Content)

View File

@@ -17,29 +17,13 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/ssh"
)
// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// ____ __.
// | |/ _|____ ___.__. ______
// | <_/ __ < | |/ ___/
// | | \ ___/\___ |\___ \
// |____|__ \___ > ____/____ >
// \/ \/\/ \/
//
// This file contains functions for creating authorized_keys files
//
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
const (
tplCommentPrefix = `# gitea public key`
tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n"
)
// AuthorizedStringCommentPrefix is a magic tag
// some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys
const AuthorizedStringCommentPrefix = `# gitea public key`
var sshOpLocker sync.Mutex
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
}
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
func AuthorizedStringForKey(key *PublicKey) string {
func AuthorizedStringForKey(key *PublicKey) (string, error) {
sb := &strings.Builder{}
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]any{
_, err := writeAuthorizedStringForKey(key, sb)
return sb.String(), err
}
// WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing.
func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
validKey, err := writeAuthorizedStringForKey(key, w)
if !validKey {
log.Debug("WriteAuthorizedStringForValidKey: key %s is not valid: %v", key, err)
return nil
}
return err
}
func writeAuthorizedStringForKey(key *PublicKey, w io.Writer) (keyValid bool, err error) {
const tpl = AuthorizedStringCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s` + "\n"
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key.Content))
if err != nil {
return false, err
}
// now the key is valid, the code below could only return template/IO related errors
sbCmd := &strings.Builder{}
err = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sbCmd, map[string]any{
"AppPath": util.ShellEscape(setting.AppPath),
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
"CustomConf": util.ShellEscape(setting.CustomConf),
"CustomPath": util.ShellEscape(setting.CustomPath),
"Key": key,
})
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
if err != nil {
return true, err
}
sshCommandEscaped := util.ShellEscape(sbCmd.String())
sshKeyMarshalled := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pubKey)))
sshKeyComment := fmt.Sprintf("user-%d", key.OwnerID)
_, err = fmt.Fprintf(w, tpl, sshCommandEscaped, sshKeyMarshalled, sshKeyComment)
return true, err
}
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
@@ -112,7 +124,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
if key.Type == KeyTypePrincipal {
continue
}
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
if err = WriteAuthorizedStringForValidKey(key, f); err != nil {
return err
}
}
@@ -120,10 +132,9 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
}
// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
func RegeneratePublicKeys(ctx context.Context, t io.Writer) error {
if err := db.GetEngine(ctx).Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean any) (err error) {
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
return err
return WriteAuthorizedStringForValidKey(bean.(*PublicKey), t)
}); err != nil {
return err
}
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, tplCommentPrefix) {
if strings.HasPrefix(line, AuthorizedStringCommentPrefix) {
scanner.Scan()
continue
}
_, err = t.WriteString(line + "\n")
_, err = io.WriteString(t, line+"\n")
if err != nil {
return err
}

View File

@@ -111,11 +111,11 @@ func (t *TwoFactor) SetSecret(secretString string) error {
func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
if err != nil {
return false, err
return false, fmt.Errorf("ValidateTOTP invalid base64: %w", err)
}
secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
if err != nil {
return false, err
return false, fmt.Errorf("ValidateTOTP unable to decrypt (maybe SECRET_KEY is wrong): %w", err)
}
secretStr := string(secretBytes)
return totp.Validate(passcode, secretStr), nil

View File

@@ -139,3 +139,24 @@
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 796
title: "update actions"
repo_id: 4
owner_id: 1
workflow_id: "artifact.yaml"
index: 191
trigger_user_id: 1
ref: "refs/heads/master"
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
event: "push"
trigger_event: "push"
is_fork_pull_request: 0
status: 5
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View File

@@ -129,3 +129,18 @@
status: 5
started: 1683636528
stopped: 1683636626
-
id: 205
run_id: 796
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 55
status: 3
started: 1683636528
stopped: 1683636626

View File

@@ -177,3 +177,24 @@
log_length: 0
log_size: 0
log_expired: 0
-
id: 55
job_id: 205
attempt: 1
runner_id: 1
status: 3 # 3 is the status code for "cancelled"
started: 1683636528
stopped: 1683636626
repo_id: 4
owner_id: 1
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
is_fork_pull_request: 0
token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab
token_salt: eeeeeeee
token_last_eight: eeeeeeee
log_filename: artifact-test2/2f/47.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0

View File

@@ -213,3 +213,15 @@
is_deleted: false
deleted_by_id: 0
deleted_unix: 0
-
id: 26
repo_id: 10
name: 'feature/1'
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
commit_message: 'Initial commit'
commit_time: 1489950479
pusher_id: 2
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

View File

@@ -733,3 +733,10 @@
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810
-
id: 111
repo_id: 4
type: 10
config: "{}"
created_unix: 946684810

View File

@@ -110,6 +110,8 @@
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 1
num_action_runs: 4
num_closed_action_runs: 3
is_private: false
is_empty: false
is_archived: false

View File

@@ -368,7 +368,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
}
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Cols("name").Update(&Branch{
Name: to,
}); err != nil {
return err

View File

@@ -5,7 +5,6 @@ package git
import (
"context"
"errors"
"fmt"
"slices"
"strings"
@@ -25,7 +24,7 @@ import (
"xorm.io/builder"
)
var ErrBranchIsProtected = errors.New("branch is protected")
var ErrBranchIsProtected = util.ErrorWrap(util.ErrPermissionDenied, "branch is protected")
// ProtectedBranch struct
type ProtectedBranch struct {
@@ -467,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
return currentWhitelist, nil
}
prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
return nil, err
}
whitelist = make([]int64, 0, len(newWhitelist))
for _, userID := range newWhitelist {
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
return nil, err
} else if !reader {
if !prUserIDs.Contains(userID) {
continue
}
whitelist = append(whitelist, userID)

View File

@@ -862,10 +862,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err
}
case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
}
// comment type reopen and close event have their own logic to update numbers but not here
}
// update the issue's updated_unix column
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")

View File

@@ -476,7 +476,7 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) {
),
builder.Eq{"issue.poster_id": subscriberID},
builder.In("issue.repo_id", builder.
Select("id").
Select("repo_id").
From("watch").
Where(builder.And(builder.Eq{"user_id": subscriberID},
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),

View File

@@ -197,6 +197,12 @@ func TestIssues(t *testing.T) {
},
[]int64{2},
},
{
issues_model.IssuesOptions{
SubscriberID: 11,
},
[]int64{11, 5, 9, 8, 3, 2, 1},
},
} {
issues, err := issues_model.Issues(t.Context(), &test.Opts)
assert.NoError(t, err)

View File

@@ -146,8 +146,19 @@ func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User
}
// update repository's issue closed number
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
return nil, err
switch cmtType {
case CommentTypeClose, CommentTypeMergePull:
// only increase closed count
if err := IncrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
return nil, err
}
case CommentTypeReopen:
// only decrease closed count
if err := DecrRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false, true); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid comment type: %d", cmtType)
}
return CreateComment(ctx, &CreateCommentOptions{
@@ -318,7 +329,6 @@ type NewIssueOptions struct {
Issue *Issue
LabelIDs []int64
Attachments []string // In UUID format.
IsPull bool
}
// NewIssueWithIndex creates issue with given index
@@ -369,7 +379,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
}
}
if err := repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.IsPull, false); err != nil {
// Update repository issue total count
if err := IncrRepoIssueNumbers(ctx, opts.Repo.ID, opts.Issue.IsPull, true); err != nil {
return err
}
@@ -439,6 +450,42 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
})
}
// IncrRepoIssueNumbers increments repository issue numbers.
func IncrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, totalOrClosed bool) error {
dbSession := db.GetEngine(ctx)
var colName string
if totalOrClosed {
colName = util.Iif(isPull, "num_pulls", "num_issues")
} else {
colName = util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
}
_, err := dbSession.Incr(colName).ID(repoID).
NoAutoCondition().NoAutoTime().
Update(new(repo_model.Repository))
return err
}
// DecrRepoIssueNumbers decrements repository issue numbers.
func DecrRepoIssueNumbers(ctx context.Context, repoID int64, isPull, includeTotal, includeClosed bool) error {
if !includeTotal && !includeClosed {
return fmt.Errorf("no numbers to decrease for repo id %d", repoID)
}
dbSession := db.GetEngine(ctx)
if includeTotal {
colName := util.Iif(isPull, "num_pulls", "num_issues")
dbSession = dbSession.Decr(colName)
}
if includeClosed {
closedColName := util.Iif(isPull, "num_closed_pulls", "num_closed_issues")
dbSession = dbSession.Decr(closedColName)
}
_, err := dbSession.ID(repoID).
NoAutoCondition().NoAutoTime().
Update(new(repo_model.Repository))
return err
}
// UpdateIssueMentions updates issue-user relations for mentioned users.
func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_model.User) error {
if len(mentions) == 0 {

View File

@@ -181,6 +181,7 @@ func updateMilestone(ctx context.Context, m *Milestone) error {
func UpdateMilestoneCounters(ctx context.Context, id int64) error {
e := db.GetEngine(ctx)
_, err := e.ID(id).
Cols("num_issues", "num_closed_issues").
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{"milestone_id": id},
)).

View File

@@ -471,13 +471,13 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
issue.Index = idx
issue.Title = util.EllipsisDisplayString(issue.Title, 255)
issue.IsPull = true
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
Attachments: uuids,
IsPull: true,
}); err != nil {
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
return err

View File

@@ -173,7 +173,7 @@ func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, mig
reviewersMap := make(map[int64][]*Review) // key is reviewer id
originalReviewersMap := make(map[int64][]*Review) // key is original author id
reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest}
countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment}
for _, review := range reviews {
if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed {
if review.OriginalAuthorID != 0 {

View File

@@ -122,6 +122,7 @@ func TestGetReviewersByIssueID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
@@ -129,6 +130,12 @@ func TestGetReviewersByIssueID(t *testing.T) {
expectedReviews := []*issues_model.Review{}
expectedReviews = append(expectedReviews,
&issues_model.Review{
ID: 5,
Reviewer: user1,
Type: issues_model.ReviewTypeComment,
UpdatedUnix: 946684810,
},
&issues_model.Review{
ID: 7,
Reviewer: org3,
@@ -167,8 +174,9 @@ func TestGetReviewersByIssueID(t *testing.T) {
for _, review := range allReviews {
assert.NoError(t, review.LoadReviewer(t.Context()))
}
if assert.Len(t, allReviews, 5) {
if assert.Len(t, allReviews, 6) {
for i, review := range allReviews {
assert.Equal(t, expectedReviews[i].ID, review.ID)
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
assert.Equal(t, expectedReviews[i].Type, review.Type)
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)

View File

@@ -21,6 +21,7 @@ func UpdateOpenMilestoneCounts(x *xorm.Engine) error {
for _, id := range openMilestoneIDs {
_, err := x.ID(id).
Cols("num_issues", "num_closed_issues").
SetExpr("num_issues", builder.Select("count(*)").From("issue").Where(
builder.Eq{"milestone_id": id},
)).

View File

@@ -429,6 +429,10 @@ func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User)
return true
}
if !setting.Service.RequireSignInViewStrict && orgOrUser.Visibility == structs.VisibleTypePublic {
return true
}
if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) {
return false
}

View File

@@ -13,7 +13,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -382,6 +384,12 @@ func TestHasOrgVisibleTypePublic(t *testing.T) {
assert.True(t, test1) // owner of org
assert.True(t, test2) // user not a part of org
assert.True(t, test3) // logged out user
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true})
require.True(t, restrictedUser.IsRestricted)
assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser))
}
func TestHasOrgVisibleTypeLimited(t *testing.T) {

View File

@@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) {
teams := make([]*Team, 0, 5)
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) {
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return teams, nil
}
err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams)
return teams, err
}
func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) {
sub := builder.Select("team_id").From("team_unit").
Where(builder.Expr("team_unit.team_id = team.id")).
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
And(builder.Expr("team_unit.access_mode >= ?", mode))
err := db.GetEngine(ctx).
err = db.GetEngine(ctx).
Select("team.id").
Table("team").
Join("INNER", "team_repo", "team_repo.team_id = team.id").
And("team_repo.org_id = ?", orgID).
And("team_repo.repo_id = ?", repoID).
And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
And(builder.Or(
builder.Expr("team.authorize >= ?", mode),
builder.In("team.id", sub),
)).
OrderBy("name").
Find(&teams)
return teams, err
Find(&teamIDs)
return teamIDs, err
}
func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) {
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return userIDs, nil
}
err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs)
return userIDs, err
}

View File

@@ -13,6 +13,8 @@ import (
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"xorm.io/builder"
)
@@ -41,7 +43,12 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
restricted = user.IsRestricted
}
if !restricted && !repo.IsPrivate {
if err := repo.LoadOwner(ctx); err != nil {
return mode, err
}
repoIsFullyPublic := !setting.Service.RequireSignInViewStrict && repo.Owner.Visibility == structs.VisibleTypePublic && !repo.IsPrivate
if (restricted && repoIsFullyPublic) || (!restricted && !repo.IsPrivate) {
mode = perm.AccessModeRead
}

View File

@@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@@ -51,7 +52,14 @@ func TestAccessLevel(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, level)
// restricted user has no access to a public repo
// restricted user has default access to a public repo if no sign-in is required
setting.Service.RequireSignInViewStrict = false
level, err = access_model.AccessLevel(t.Context(), user29, repo1)
assert.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, level)
// restricted user has no access to a public repo if sign-in is required
setting.Service.RequireSignInViewStrict = true
level, err = access_model.AccessLevel(t.Context(), user29, repo1)
assert.NoError(t, err)
assert.Equal(t, perm_model.AccessModeNone, level)

View File

@@ -14,6 +14,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -458,54 +459,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
return perm.HasAnyUnitAccess(), nil
}
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
if err = repo.LoadOwner(ctx); err != nil {
func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) {
userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType)
if err != nil {
return nil, err
}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
if len(userIDs) == 0 {
return users, nil
}
if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil {
return nil, err
}
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(accesses)+1)
if len(accesses) > 0 {
userIDs := make([]int64, len(accesses))
for i := 0; i < len(accesses); i++ {
userIDs[i] = accesses[i].UserID
}
if err = e.In("id", userIDs).Find(&users); err != nil {
return nil, err
}
}
if !repo.Owner.IsOrganization() {
users = append(users, repo.Owner)
}
return users, nil
}
// GetRepoReaders returns all users that have explicit read access or higher to the repository.
func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead)
}
// GetRepoWriters returns all users that have write access to the repository.
func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
}
// IsRepoReader returns true if user has explicit read access or higher to the repository.
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
if repo.OwnerID == userID {
return true, nil
func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) {
userIDs := container.Set[int64]{}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
return nil, err
}
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
for _, a := range accesses {
userIDs.Add(a.UserID)
}
if err := repo.LoadOwner(ctx); err != nil {
return nil, err
}
if !repo.Owner.IsOrganization() {
userIDs.Add(repo.Owner.ID)
} else {
teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType)
if err != nil {
return nil, err
}
userIDs.AddMultiple(teamUserIDs...)
}
return userIDs, nil
}
// CheckRepoUnitUser check whether user could visit the unit of this repository

View File

@@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
require.NoError(t, db.Insert(ctx, team))
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
perm, err := GetUserRepoPermission(ctx, repo32, user)
require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
@@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Empty(t, users)
})
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
@@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
})
}

View File

@@ -49,6 +49,19 @@ func init() {
db.RegisterModel(new(ReviewState))
}
func (rs *ReviewState) GetViewedFileCount() int {
if len(rs.UpdatedFiles) == 0 {
return 0
}
var numViewedFiles int
for _, state := range rs.UpdatedFiles {
if state == Viewed {
numViewedFiles++
}
}
return numViewedFiles
}
// GetReviewState returns the ReviewState with all given values prefilled, whether or not it exists in the database.
// If the review didn't exist before in the database, it won't afterwards either.
// The returned boolean shows whether the review exists in the database
@@ -60,18 +73,18 @@ func GetReviewState(ctx context.Context, userID, pullID int64, commitSHA string)
// UpdateReviewState updates the given review inside the database, regardless of whether it existed before or not
// The given map of files with their viewed state will be merged with the previous review, if present
func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) error {
func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA string, updatedFiles map[string]ViewedState) (*ReviewState, error) {
log.Trace("Updating review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, updatedFiles)
review, exists, err := GetReviewState(ctx, userID, pullID, commitSHA)
if err != nil {
return err
return nil, err
}
if exists {
review.UpdatedFiles = mergeFiles(review.UpdatedFiles, updatedFiles)
} else if previousReview, err := getNewestReviewStateApartFrom(ctx, userID, pullID, commitSHA); err != nil {
return err
return nil, err
// Overwrite the viewed files of the previous review if present
} else if previousReview != nil {
@@ -85,11 +98,11 @@ func UpdateReviewState(ctx context.Context, userID, pullID int64, commitSHA stri
if !exists {
log.Trace("Inserting new review for user %d, repo %d, commit %s with the updated files %v.", userID, pullID, commitSHA, review.UpdatedFiles)
_, err := engine.Insert(review)
return err
return nil, err
}
log.Trace("Updating already existing review with ID %d (user %d, repo %d, commit %s) with the updated files %v.", review.ID, userID, pullID, commitSHA, review.UpdatedFiles)
_, err = engine.ID(review.ID).Update(&ReviewState{UpdatedFiles: review.UpdatedFiles})
return err
_, err = engine.ID(review.ID).Cols("updated_files").Update(review)
return review, err
}
// mergeFiles merges the given maps of files with their viewing state into one map.

View File

@@ -642,6 +642,17 @@ func SearchRepositoryIDsByCondition(ctx context.Context, cond builder.Cond) ([]i
Find(&repoIDs)
}
func userAllPublicRepoCond(cond builder.Cond, orgVisibilityLimit []structs.VisibleType) builder.Cond {
return cond.Or(builder.And(
builder.Eq{"`repository`.is_private": false},
// Aren't in a private organisation or limited organisation if we're not logged in
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
builder.And(
builder.Eq{"type": user_model.UserTypeOrganization},
builder.In("visibility", orgVisibilityLimit)),
))))
}
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond {
cond := builder.NewCond()
@@ -651,15 +662,8 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
if user == nil || user.ID <= 0 {
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited)
}
// 1. Be able to see all non-private repositories that either:
cond = cond.Or(builder.And(
builder.Eq{"`repository`.is_private": false},
// 2. Aren't in an private organisation or limited organisation if we're not logged in
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
builder.And(
builder.Eq{"type": user_model.UserTypeOrganization},
builder.In("visibility", orgVisibilityLimit)),
))))
// 1. Be able to see all non-private repositories
cond = userAllPublicRepoCond(cond, orgVisibilityLimit)
}
if user != nil {
@@ -683,6 +687,9 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
if !user.IsRestricted {
// 5. Be able to see all public repos in private organizations that we are an org_user of
cond = cond.Or(userOrgPublicRepoCond(user.ID))
} else if !setting.Service.RequireSignInViewStrict {
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate, structs.VisibleTypeLimited}
cond = userAllPublicRepoCond(cond, orgVisibilityLimit)
}
}

View File

@@ -10,9 +10,14 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func getTestCases() []struct {
@@ -182,7 +187,16 @@ func getTestCases() []struct {
func TestSearchRepository(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
t.Run("SearchRepositoryPublic", testSearchRepositoryPublic)
t.Run("SearchRepositoryPublicRestricted", testSearchRepositoryRestricted)
t.Run("SearchRepositoryPrivate", testSearchRepositoryPrivate)
t.Run("SearchRepositoryNonExistingOwner", testSearchRepositoryNonExistingOwner)
t.Run("SearchRepositoryWithInDescription", testSearchRepositoryWithInDescription)
t.Run("SearchRepositoryNotInDescription", testSearchRepositoryNotInDescription)
t.Run("SearchRepositoryCases", testSearchRepositoryCases)
}
func testSearchRepositoryPublic(t *testing.T) {
// test search public repository on explore page
repos, count, err := repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
@@ -211,9 +225,54 @@ func TestSearchRepository(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
assert.Len(t, repos, 2)
}
func testSearchRepositoryRestricted(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true})
performSearch := func(t *testing.T, user *user_model.User) (publicRepoIDs []int64) {
repos, count, err := repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{Page: 1, PageSize: 10000},
Actor: user,
})
require.NoError(t, err)
assert.Len(t, repos, int(count))
for _, repo := range repos {
require.NoError(t, repo.LoadOwner(t.Context()))
if repo.Owner.Visibility == structs.VisibleTypePublic && !repo.IsPrivate {
publicRepoIDs = append(publicRepoIDs, repo.ID)
}
}
return publicRepoIDs
}
normalPublicRepoIDs := performSearch(t, user2)
require.Greater(t, len(normalPublicRepoIDs), 10) // quite a lot
t.Run("RestrictedUser-NoSignInRequirement", func(t *testing.T) {
// restricted user can also see public repositories if no "required sign-in"
repoIDs := performSearch(t, restrictedUser)
assert.ElementsMatch(t, normalPublicRepoIDs, repoIDs)
})
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
t.Run("NormalUser-RequiredSignIn", func(t *testing.T) {
// normal user can still see all public repos, not affected by "required sign-in"
repoIDs := performSearch(t, user2)
assert.ElementsMatch(t, normalPublicRepoIDs, repoIDs)
})
t.Run("RestrictedUser-RequiredSignIn", func(t *testing.T) {
// restricted user can see only their own repo
repoIDs := performSearch(t, restrictedUser)
assert.Equal(t, []int64{4}, repoIDs)
})
}
func testSearchRepositoryPrivate(t *testing.T) {
// test search private repository on explore page
repos, count, err = repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{
repos, count, err := repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -242,16 +301,18 @@ func TestSearchRepository(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(3), count)
assert.Len(t, repos, 3)
}
// Test non existing owner
repos, count, err = repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID})
func testSearchRepositoryNonExistingOwner(t *testing.T) {
repos, count, err := repo_model.SearchRepositoryByName(t.Context(), repo_model.SearchRepoOptions{OwnerID: unittest.NonexistentID})
assert.NoError(t, err)
assert.Empty(t, repos)
assert.Equal(t, int64(0), count)
}
// Test search within description
repos, count, err = repo_model.SearchRepository(t.Context(), repo_model.SearchRepoOptions{
func testSearchRepositoryWithInDescription(t *testing.T) {
repos, count, err := repo_model.SearchRepository(t.Context(), repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -266,9 +327,10 @@ func TestSearchRepository(t *testing.T) {
assert.Equal(t, "test_repo_14", repos[0].Name)
}
assert.Equal(t, int64(1), count)
}
// Test NOT search within description
repos, count, err = repo_model.SearchRepository(t.Context(), repo_model.SearchRepoOptions{
func testSearchRepositoryNotInDescription(t *testing.T) {
repos, count, err := repo_model.SearchRepository(t.Context(), repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
Page: 1,
PageSize: 10,
@@ -281,7 +343,9 @@ func TestSearchRepository(t *testing.T) {
assert.NoError(t, err)
assert.Empty(t, repos)
assert.Equal(t, int64(0), count)
}
func testSearchRepositoryCases(t *testing.T) {
testCases := getTestCases()
for _, testCase := range testCases {

View File

@@ -159,7 +159,7 @@ func RemoveTopicsFromRepo(ctx context.Context, repoID int64) error {
builder.In("id",
builder.Select("topic_id").From("repo_topic").Where(builder.Eq{"repo_id": repoID}),
),
).Cols("repo_count").SetExpr("repo_count", "repo_count-1").Update(&Topic{})
).Decr("repo_count").Update(&Topic{})
if err != nil {
return err
}

View File

@@ -127,16 +127,9 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) {
for _, upload := range uploads {
localPath := upload.LocalPath()
isFile, err := util.IsFile(localPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", localPath, err)
}
if !isFile {
continue
}
if err := util.Remove(localPath); err != nil {
return fmt.Errorf("remove upload: %w", err)
// just continue, don't fail the whole operation if a file is missing (removed by others)
log.Error("unable to remove upload file %s: %v", localPath, err)
}
}

View File

@@ -178,8 +178,8 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[
for _, secret := range append(ownerSecrets, repoSecrets...) {
v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data)
if err != nil {
log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err)
return nil, err
log.Error("Unable to decrypt Actions secret %v %q, maybe SECRET_KEY is wrong: %v", secret.ID, secret.Name, err)
continue
}
secrets[secret.Name] = v
}

View File

@@ -5,7 +5,6 @@ package actions
import (
"bytes"
"io"
"slices"
"strings"
@@ -13,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/nektos/act/pkg/jobparser"
@@ -77,7 +77,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
if err != nil {
return nil, err
}
content, err := io.ReadAll(f)
content, err := util.ReadWithLimit(f, 1024*1024)
_ = f.Close()
if err != nil {
return nil, err

View File

@@ -26,9 +26,11 @@ type ConvertOpts struct {
KeepBOM bool
}
var ToUTF8WithFallbackReaderPrefetchSize = 16 * 1024
// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
buf := make([]byte, 2048)
buf := make([]byte, ToUTF8WithFallbackReaderPrefetchSize)
n, err := util.ReadAtMost(rd, buf)
if err != nil {
return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd)
@@ -41,6 +43,7 @@ func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
encoding, _ := charset.Lookup(charsetLabel)
if encoding == nil {
log.Error("Unknown encoding: %s", charsetLabel)
return io.MultiReader(bytes.NewReader(buf[:n]), rd)
}
@@ -54,17 +57,18 @@ func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
}
// ToUTF8 converts content to UTF8 encoding
func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
func ToUTF8(content []byte, opts ConvertOpts) ([]byte, error) {
charsetLabel, err := DetectEncoding(content)
if err != nil {
return "", err
return content, err
} else if charsetLabel == "UTF-8" {
return string(MaybeRemoveBOM(content, opts)), nil
return MaybeRemoveBOM(content, opts), nil
}
encoding, _ := charset.Lookup(charsetLabel)
if encoding == nil {
return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
log.Error("Unknown encoding: %s", charsetLabel)
return content, fmt.Errorf("unknown encoding: %s", charsetLabel)
}
// If there is an error, we concatenate the nicely decoded part and the
@@ -76,7 +80,7 @@ func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
result = MaybeRemoveBOM(result, opts)
return string(result), err
return result, err
}
// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
@@ -94,6 +98,7 @@ func ToUTF8DropErrors(content []byte, opts ConvertOpts) []byte {
encoding, _ := charset.Lookup(charsetLabel)
if encoding == nil {
log.Error("Unknown encoding: %s", charsetLabel)
return content
}
@@ -130,28 +135,37 @@ func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
}
// DetectEncoding detect the encoding of content
func DetectEncoding(content []byte) (string, error) {
// it always returns a detected or guessed "encoding" string, no matter error happens or not
func DetectEncoding(content []byte) (encoding string, _ error) {
// First we check if the content represents valid utf8 content excepting a truncated character at the end.
// Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
// instead we walk backwards from the end to trim off a the incomplete character
// instead we walk backwards from the end to trim off the incomplete character
toValidate := content
end := len(toValidate) - 1
if end < 0 {
// no-op
} else if toValidate[end]>>5 == 0b110 {
// Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
toValidate = toValidate[:end]
} else if end > 0 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>4 == 0b1110 {
// Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
toValidate = toValidate[:end-1]
} else if end > 1 && toValidate[end]>>6 == 0b10 && toValidate[end-1]>>6 == 0b10 && toValidate[end-2]>>3 == 0b11110 {
// Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
toValidate = toValidate[:end-2]
// U+0000 U+007F 0yyyzzzz
// U+0080 U+07FF 110xxxyy 10yyzzzz
// U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
// U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
cnt := 0
for end >= 0 && cnt < 4 {
c := toValidate[end]
if c>>5 == 0b110 || c>>4 == 0b1110 || c>>3 == 0b11110 {
// a leading byte
toValidate = toValidate[:end]
break
} else if c>>6 == 0b10 {
// a continuation byte
end--
} else {
// not an utf-8 byte
break
}
cnt++
}
if utf8.Valid(toValidate) {
log.Debug("Detected encoding: utf-8 (fast)")
return "UTF-8", nil
}
@@ -160,7 +174,7 @@ func DetectEncoding(content []byte) (string, error) {
if len(content) < 1024 {
// Check if original content is valid
if _, err := textDetector.DetectBest(content); err != nil {
return "", err
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
}
times := 1024 / len(content)
detectContent = make([]byte, 0, times*len(content))
@@ -171,14 +185,10 @@ func DetectEncoding(content []byte) (string, error) {
detectContent = content
}
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie-break
results, err := textDetector.DetectAll(detectContent)
if err != nil {
if err == chardet.NotDetectedError && len(setting.Repository.AnsiCharset) > 0 {
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
return setting.Repository.AnsiCharset, nil
}
return "", err
return util.IfZero(setting.Repository.AnsiCharset, "UTF-8"), err
}
topConfidence := results[0].Confidence
@@ -201,11 +211,9 @@ func DetectEncoding(content []byte) (string, error) {
}
// FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
if topResult.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
if topResult.Charset != "UTF-8" && setting.Repository.AnsiCharset != "" {
return setting.Repository.AnsiCharset, err
}
log.Debug("Detected encoding: %s", topResult.Charset)
return topResult.Charset, err
return topResult.Charset, nil
}

View File

@@ -4,12 +4,12 @@
package charset
import (
"bytes"
"io"
"strings"
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -47,12 +47,12 @@ func TestToUTF8(t *testing.T) {
res, err := ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{})
assert.NoError(t, err)
assert.Equal(t, "ABC", res)
assert.Equal(t, "ABC", string(res))
// "áéíóú"
res, err = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{})
assert.NoError(t, err)
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
// "áéíóú"
res, err = ToUTF8([]byte{
@@ -60,7 +60,7 @@ func TestToUTF8(t *testing.T) {
0xc3, 0xba,
}, ConvertOpts{})
assert.NoError(t, err)
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res))
assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res)
res, err = ToUTF8([]byte{
0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63,
@@ -96,12 +96,11 @@ func TestToUTF8(t *testing.T) {
assert.Equal(t, []byte{
0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3,
0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82,
},
[]byte(res))
}, res)
res, err = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{})
assert.NoError(t, err)
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res))
assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res)
}
func TestToUTF8WithFallback(t *testing.T) {
@@ -231,152 +230,42 @@ func TestDetectEncoding(t *testing.T) {
assert.Error(t, err)
}
func stringMustStartWith(t *testing.T, expected, value string) {
assert.Equal(t, expected, value[:len(expected)])
func stringMustStartWith(t *testing.T, expected string, value []byte) {
assert.Equal(t, expected, string(value[:len(expected)]))
}
func stringMustEndWith(t *testing.T, expected, value string) {
assert.Equal(t, expected, value[len(value)-len(expected):])
func stringMustEndWith(t *testing.T, expected string, value []byte) {
assert.Equal(t, expected, string(value[len(value)-len(expected):]))
}
func TestToUTF8WithFallbackReader(t *testing.T) {
resetDefaultCharsetsOrder()
test.MockVariableValue(&ToUTF8WithFallbackReaderPrefetchSize)
for testLen := range 2048 {
pattern := " test { () }\n"
input := ""
for len(input) < testLen {
input += pattern
}
input = input[:testLen]
input += "// Выключаем"
rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)), ConvertOpts{})
block := "aá啊🤔"
runes := []rune(block)
assert.Len(t, string(runes[0]), 1)
assert.Len(t, string(runes[1]), 2)
assert.Len(t, string(runes[2]), 3)
assert.Len(t, string(runes[3]), 4)
content := strings.Repeat(block, 2)
for i := 1; i < len(content); i++ {
encoding, err := DetectEncoding([]byte(content[:i]))
assert.NoError(t, err)
assert.Equal(t, "UTF-8", encoding)
ToUTF8WithFallbackReaderPrefetchSize = i
rd := ToUTF8WithFallbackReader(strings.NewReader(content), ConvertOpts{})
r, _ := io.ReadAll(rd)
assert.Equalf(t, input, string(r), "testing string len=%d", testLen)
assert.Equal(t, content, string(r))
}
for _, r := range runes {
content = "abc abc " + string(r) + string(r) + string(r)
for i := 0; i < len(content); i++ {
encoding, err := DetectEncoding([]byte(content[:i]))
assert.NoError(t, err)
assert.Equal(t, "UTF-8", encoding)
}
}
truncatedOneByteExtension := failFastBytes
encoding, _ := DetectEncoding(truncatedOneByteExtension)
assert.Equal(t, "UTF-8", encoding)
truncatedTwoByteExtension := failFastBytes
truncatedTwoByteExtension[len(failFastBytes)-1] = 0x9b
truncatedTwoByteExtension[len(failFastBytes)-2] = 0xe2
encoding, _ = DetectEncoding(truncatedTwoByteExtension)
assert.Equal(t, "UTF-8", encoding)
truncatedThreeByteExtension := failFastBytes
truncatedThreeByteExtension[len(failFastBytes)-1] = 0x92
truncatedThreeByteExtension[len(failFastBytes)-2] = 0x9f
truncatedThreeByteExtension[len(failFastBytes)-3] = 0xf0
encoding, _ = DetectEncoding(truncatedThreeByteExtension)
assert.Equal(t, "UTF-8", encoding)
}
var failFastBytes = []byte{
0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67, 0x2e, 0x61, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x74, 0x6f,
0x6f, 0x6c, 0x73, 0x2e, 0x61, 0x6e, 0x74, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x64, 0x65, 0x66, 0x73, 0x2e, 0x63, 0x6f, 0x6e,
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4f, 0x73, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x72, 0x67,
0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f,
0x74, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x6c, 0x65, 0x2e, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x2e, 0x42,
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x0a, 0x0a, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x64, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d,
0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65,
0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x61, 0x70, 0x69, 0x2d, 0x64, 0x6f, 0x63, 0x73, 0x22, 0x29,
0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x64, 0x62,
0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x66,
0x73, 0x22, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x28, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x22, 0x3a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x3a, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x71, 0x22, 0x29, 0x29, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22,
0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
0x2d, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74,
0x65, 0x72, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x68, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e,
0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x29, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28,
0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b,
0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74,
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x77, 0x65, 0x62, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69,
0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72,
0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x6f, 0x70,
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f,
0x72, 0x6b, 0x2e, 0x62, 0x6f, 0x6f, 0x74, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x2d,
0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f,
0x72, 0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63,
0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74,
0x61, 0x72, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
0x72, 0x74, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2d, 0x61, 0x6c, 0x6c, 0x22, 0x29, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72,
0x67, 0x2e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x63, 0x6c,
0x6f, 0x75, 0x64, 0x3a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x74, 0x61,
0x72, 0x74, 0x65, 0x72, 0x2d, 0x73, 0x6c, 0x65, 0x75, 0x74, 0x68, 0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x73, 0x70,
0x72, 0x69, 0x6e, 0x67, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x72, 0x65, 0x74, 0x72, 0x79, 0x3a,
0x73, 0x70, 0x72, 0x69, 0x6e, 0x67, 0x2d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x63, 0x68, 0x2e, 0x71,
0x6f, 0x73, 0x2e, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x6c, 0x6f, 0x67, 0x62, 0x61, 0x63, 0x6b, 0x2d, 0x63,
0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x69, 0x6f, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65,
0x74, 0x65, 0x72, 0x3a, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x72, 0x79, 0x2d, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x6f, 0x74,
0x6c, 0x69, 0x6e, 0x28, 0x22, 0x73, 0x74, 0x64, 0x6c, 0x69, 0x62, 0x22, 0x29, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x22, 0x6a,
0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x65, 0x3a, 0x70, 0x65, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2d,
0x74, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a,
0x61, 0x72, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
0x69, 0x6e, 0x67, 0x28, 0x4a, 0x61, 0x72, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e,
0x73, 0x65, 0x74, 0x28, 0x22, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68,
0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x67,
0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x28, 0x22, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x2d, 0x50, 0x61, 0x74, 0x68, 0x22, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62,
0x6a, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x3d,
0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x2b, 0x22, 0x2e, 0x74, 0x6f, 0x52, 0x65, 0x67, 0x65, 0x78, 0x28, 0x29,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x20, 0x66, 0x75, 0x6e, 0x20, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x3a, 0x20, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x70,
0x61, 0x74, 0x68, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x53, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x28, 0x22, 0x20, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x2e, 0x74, 0x6f, 0x55, 0x52, 0x49, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x55,
0x52, 0x4c, 0x28, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x72, 0x65, 0x70, 0x6c,
0x61, 0x63, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x28, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x2c, 0x20, 0x22, 0x2f,
0x22, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x73,
0x6b, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x3c, 0x42, 0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x3e, 0x28, 0x22, 0x62,
0x6f, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x22, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4f,
0x73, 0x2e, 0x69, 0x73, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x28, 0x4f, 0x73, 0x2e, 0x46, 0x41, 0x4d, 0x49, 0x4c, 0x59,
0x5f, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x28, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x74, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x28, 0x22, 0x6d, 0x61, 0x69,
0x6e, 0x22, 0x29, 0x2e, 0x6d, 0x61, 0x70, 0x20, 0x7b, 0x20, 0x69, 0x74, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20,
0x7d, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x63, 0x68, 0x4a, 0x61, 0x72, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0xd0,
}

View File

@@ -8,7 +8,9 @@ import (
"io"
"sort"
"strings"
"sync"
"sync/atomic"
"code.gitea.io/gitea/modules/setting"
)
// Gemoji is a set of emoji data.
@@ -23,74 +25,78 @@ type Emoji struct {
SkinTones bool
}
var (
// codeMap provides a map of the emoji unicode code to its emoji data.
codeMap map[string]int
type globalVarsStruct struct {
codeMap map[string]int // emoji unicode code to its emoji data.
aliasMap map[string]int // the alias to its emoji data.
emptyReplacer *strings.Replacer // string replacer for emoji codes, used for finding emoji positions.
codeReplacer *strings.Replacer // string replacer for emoji codes.
aliasReplacer *strings.Replacer // string replacer for emoji aliases.
}
// aliasMap provides a map of the alias to its emoji data.
aliasMap map[string]int
var globalVarsStore atomic.Pointer[globalVarsStruct]
// emptyReplacer is the string replacer for emoji codes.
emptyReplacer *strings.Replacer
func globalVars() *globalVarsStruct {
vars := globalVarsStore.Load()
if vars != nil {
return vars
}
// although there can be concurrent calls, the result should be the same, and there is no performance problem
vars = &globalVarsStruct{}
vars.codeMap = make(map[string]int, len(GemojiData))
vars.aliasMap = make(map[string]int, len(GemojiData))
// codeReplacer is the string replacer for emoji codes.
codeReplacer *strings.Replacer
// process emoji codes and aliases
codePairs := make([]string, 0)
emptyPairs := make([]string, 0)
aliasPairs := make([]string, 0)
// aliasReplacer is the string replacer for emoji aliases.
aliasReplacer *strings.Replacer
// sort from largest to small so we match combined emoji first
sort.Slice(GemojiData, func(i, j int) bool {
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
})
once sync.Once
)
func loadMap() {
once.Do(func() {
// initialize
codeMap = make(map[string]int, len(GemojiData))
aliasMap = make(map[string]int, len(GemojiData))
// process emoji codes and aliases
codePairs := make([]string, 0)
emptyPairs := make([]string, 0)
aliasPairs := make([]string, 0)
// sort from largest to small so we match combined emoji first
sort.Slice(GemojiData, func(i, j int) bool {
return len(GemojiData[i].Emoji) > len(GemojiData[j].Emoji)
})
for i, e := range GemojiData {
if e.Emoji == "" || len(e.Aliases) == 0 {
continue
}
// setup codes
codeMap[e.Emoji] = i
codePairs = append(codePairs, e.Emoji, ":"+e.Aliases[0]+":")
emptyPairs = append(emptyPairs, e.Emoji, e.Emoji)
// setup aliases
for _, a := range e.Aliases {
if a == "" {
continue
}
aliasMap[a] = i
aliasPairs = append(aliasPairs, ":"+a+":", e.Emoji)
}
for idx, emoji := range GemojiData {
if emoji.Emoji == "" || len(emoji.Aliases) == 0 {
continue
}
// create replacers
emptyReplacer = strings.NewReplacer(emptyPairs...)
codeReplacer = strings.NewReplacer(codePairs...)
aliasReplacer = strings.NewReplacer(aliasPairs...)
})
// process aliases
firstAlias := ""
for _, alias := range emoji.Aliases {
if alias == "" {
continue
}
enabled := len(setting.UI.EnabledEmojisSet) == 0 || setting.UI.EnabledEmojisSet.Contains(alias)
if !enabled {
continue
}
if firstAlias == "" {
firstAlias = alias
}
vars.aliasMap[alias] = idx
aliasPairs = append(aliasPairs, ":"+alias+":", emoji.Emoji)
}
// process emoji code
if firstAlias != "" {
vars.codeMap[emoji.Emoji] = idx
codePairs = append(codePairs, emoji.Emoji, ":"+emoji.Aliases[0]+":")
emptyPairs = append(emptyPairs, emoji.Emoji, emoji.Emoji)
}
}
// create replacers
vars.emptyReplacer = strings.NewReplacer(emptyPairs...)
vars.codeReplacer = strings.NewReplacer(codePairs...)
vars.aliasReplacer = strings.NewReplacer(aliasPairs...)
globalVarsStore.Store(vars)
return vars
}
// FromCode retrieves the emoji data based on the provided unicode code (ie,
// "\u2618" will return the Gemoji data for "shamrock").
func FromCode(code string) *Emoji {
loadMap()
i, ok := codeMap[code]
i, ok := globalVars().codeMap[code]
if !ok {
return nil
}
@@ -102,12 +108,11 @@ func FromCode(code string) *Emoji {
// "alias" or ":alias:" (ie, "shamrock" or ":shamrock:" will return the Gemoji
// data for "shamrock").
func FromAlias(alias string) *Emoji {
loadMap()
if strings.HasPrefix(alias, ":") && strings.HasSuffix(alias, ":") {
alias = alias[1 : len(alias)-1]
}
i, ok := aliasMap[alias]
i, ok := globalVars().aliasMap[alias]
if !ok {
return nil
}
@@ -119,15 +124,13 @@ func FromAlias(alias string) *Emoji {
// alias (in the form of ":alias:") (ie, "\u2618" will be converted to
// ":shamrock:").
func ReplaceCodes(s string) string {
loadMap()
return codeReplacer.Replace(s)
return globalVars().codeReplacer.Replace(s)
}
// ReplaceAliases replaces all aliases of the form ":alias:" with its
// corresponding unicode value.
func ReplaceAliases(s string) string {
loadMap()
return aliasReplacer.Replace(s)
return globalVars().aliasReplacer.Replace(s)
}
type rememberSecondWriteWriter struct {
@@ -163,7 +166,6 @@ func (n *rememberSecondWriteWriter) WriteString(s string) (int, error) {
// FindEmojiSubmatchIndex returns index pair of longest emoji in a string
func FindEmojiSubmatchIndex(s string) []int {
loadMap()
secondWriteWriter := rememberSecondWriteWriter{}
// A faster and clean implementation would copy the trie tree formation in strings.NewReplacer but
@@ -175,7 +177,7 @@ func FindEmojiSubmatchIndex(s string) []int {
// Therefore we can simply take the index of the second write as our first emoji
//
// FIXME: just copy the trie implementation from strings.NewReplacer
_, _ = emptyReplacer.WriteString(&secondWriteWriter, s)
_, _ = globalVars().emptyReplacer.WriteString(&secondWriteWriter, s)
// if we wrote less than twice then we never "replaced"
if secondWriteWriter.writecount < 2 {

View File

@@ -7,14 +7,13 @@ package emoji
import (
"testing"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestDumpInfo(t *testing.T) {
t.Logf("codes: %d", len(codeMap))
t.Logf("aliases: %d", len(aliasMap))
}
func TestLookup(t *testing.T) {
a := FromCode("\U0001f37a")
b := FromCode("🍺")
@@ -24,7 +23,6 @@ func TestLookup(t *testing.T) {
assert.Equal(t, a, b)
assert.Equal(t, b, c)
assert.Equal(t, c, d)
assert.Equal(t, a, d)
m := FromCode("\U0001f44d")
n := FromAlias(":thumbsup:")
@@ -32,7 +30,20 @@ func TestLookup(t *testing.T) {
assert.Equal(t, m, n)
assert.Equal(t, m, o)
assert.Equal(t, n, o)
defer test.MockVariableValue(&setting.UI.EnabledEmojisSet, container.SetOf("thumbsup"))()
defer globalVarsStore.Store(nil)
globalVarsStore.Store(nil)
a = FromCode("\U0001f37a")
c = FromAlias(":beer:")
m = FromCode("\U0001f44d")
n = FromAlias(":thumbsup:")
o = FromAlias("+1")
assert.Nil(t, a)
assert.Nil(t, c)
assert.NotNil(t, m)
assert.NotNil(t, n)
assert.Nil(t, o)
}
func TestReplacers(t *testing.T) {

View File

@@ -76,7 +76,7 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
if p.IconSVGs[svgID] == "" {
p.IconSVGs[svgID] = svgHTML
}
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
return template.HTML(`<svg ` + svgCommonAttrs + `><use href="#` + svgID + `"></use></svg>`)
}
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {

View File

@@ -25,7 +25,7 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
return ""
}
sb := &strings.Builder{}
sb.WriteString(`<div class=tw-hidden>`)
sb.WriteString(`<div class="svg-icon-container">`)
for _, icon := range p.IconSVGs {
sb.WriteString(string(icon))
}

View File

@@ -47,30 +47,16 @@ func GetHook(repoPath, name string) (*Hook, error) {
name: name,
path: filepath.Join(repoPath, "hooks", name+".d", name),
}
isFile, err := util.IsFile(h.path)
if err != nil {
return nil, err
}
if isFile {
data, err := os.ReadFile(h.path)
if err != nil {
return nil, err
}
if data, err := os.ReadFile(h.path); err == nil {
h.IsActive = true
h.Content = string(data)
return h, nil
} else if !os.IsNotExist(err) {
return nil, err
}
samplePath := filepath.Join(repoPath, "hooks", name+".sample")
isFile, err = util.IsFile(samplePath)
if err != nil {
return nil, err
}
if isFile {
data, err := os.ReadFile(samplePath)
if err != nil {
return nil, err
}
if data, err := os.ReadFile(samplePath); err == nil {
h.Sample = string(data)
}
return h, nil

View File

@@ -19,12 +19,17 @@ type TreeEntry struct {
gogitTreeEntry *object.TreeEntry
ptree *Tree
fullName string
size int64
sized bool
}
// Name returns the name of the entry
func (te *TreeEntry) Name() string {
if te.fullName != "" {
return te.fullName
}
return te.gogitTreeEntry.Name
}

View File

@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
seen := map[plumbing.Hash]bool{}
walker := object.NewTreeWalker(t.gogitTree, true, seen)
for {
_, entry, err := walker.Next()
fullName, entry, err := walker.Next()
if err == io.EOF {
break
}
@@ -84,6 +84,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry,
ptree: t,
fullName: fullName,
}
entries = append(entries, convertedEntry)
}

View File

@@ -34,12 +34,12 @@ func TestParseGitURLs(t *testing.T) {
},
},
{
kase: "git@[fe80:14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
kase: "git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git",
expected: &GitURL{
URL: &url.URL{
Scheme: "ssh",
User: url.User("git"),
Host: "[fe80:14fc:cec5:c174:d88%10]",
Host: "[fe80::14fc:cec5:c174:d88%10]",
Path: "go-gitea/gitea.git",
},
extraMark: 1,
@@ -137,11 +137,11 @@ func TestParseGitURLs(t *testing.T) {
},
},
{
kase: "https://[fe80:14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
kase: "https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git",
expected: &GitURL{
URL: &url.URL{
Scheme: "https",
Host: "[fe80:14fc:cec5:c174:d88%10]:20",
Host: "[fe80::14fc:cec5:c174:d88%10]:20",
Path: "/go-gitea/gitea.git",
},
extraMark: 0,

View File

@@ -6,7 +6,6 @@ package git
import (
"crypto/sha1"
"encoding/hex"
"io"
"strconv"
"strings"
"sync"
@@ -68,32 +67,6 @@ func ParseBool(value string) (result, valid bool) {
return intValue != 0, true
}
// LimitedReaderCloser is a limited reader closer
type LimitedReaderCloser struct {
R io.Reader
C io.Closer
N int64
}
// Read implements io.Reader
func (l *LimitedReaderCloser) Read(p []byte) (n int, err error) {
if l.N <= 0 {
_ = l.C.Close()
return 0, io.EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return n, err
}
// Close implements io.Closer
func (l *LimitedReaderCloser) Close() error {
return l.C.Close()
}
func HashFilePathForWebUI(s string) string {
h := sha1.New()
_, _ = h.Write([]byte(s))

View File

@@ -4,7 +4,10 @@
package hcaptcha
import (
"errors"
"io"
"net/http"
"net/url"
"os"
"strings"
"testing"
@@ -21,6 +24,33 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
type mockTransport struct{}
func (mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if req.URL.String() != verifyURL {
return nil, errors.New("unsupported url")
}
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
bodyValues, err := url.ParseQuery(string(body))
if err != nil {
return nil, err
}
var responseText string
if bodyValues.Get("response") == dummyToken {
responseText = `{"success":true,"credit":false,"hostname":"dummy-key-pass","challenge_ts":"2025-10-08T16:02:56.136Z"}`
} else {
responseText = `{"success":false,"error-codes":["invalid-input-response"]}`
}
return &http.Response{Request: req, Body: io.NopCloser(strings.NewReader(responseText))}, nil
}
func TestCaptcha(t *testing.T) {
tt := []struct {
Name string
@@ -54,7 +84,8 @@ func TestCaptcha(t *testing.T) {
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
client, err := New(tc.Secret, WithHTTP(&http.Client{
Timeout: time.Second * 5,
Timeout: time.Second * 5,
Transport: mockTransport{},
}))
if err != nil {
// The only error that can be returned from creating a client

View File

@@ -7,54 +7,53 @@ package httplib
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
var defaultSetting = Settings{"GiteaServer", 60 * time.Second, 60 * time.Second, nil, nil}
// newRequest returns *Request with specific method
func newRequest(url, method string) *Request {
var resp http.Response
req := http.Request{
Method: method,
Header: make(http.Header),
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
var defaultTransport = sync.OnceValue(func() http.RoundTripper {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: DialContextWithTimeout(10 * time.Second), // it is good enough in modern days
}
})
func DialContextWithTimeout(timeout time.Duration) func(ctx context.Context, network, address string) (net.Conn, error) {
return func(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{Timeout: timeout}).DialContext(ctx, network, address)
}
return &Request{url, &req, map[string]string{}, defaultSetting, &resp, nil}
}
// NewRequest returns *Request with specific method
func NewRequest(url, method string) *Request {
return newRequest(url, method)
return &Request{
url: url,
req: &http.Request{
Method: method,
Header: make(http.Header),
Proto: "HTTP/1.1", // FIXME: from legacy httplib, it shouldn't be hardcoded
ProtoMajor: 1,
ProtoMinor: 1,
},
params: map[string]string{},
// ATTENTION: from legacy httplib, callers must pay more attention to it, it will cause annoying bugs when the response takes a long time
readWriteTimeout: 60 * time.Second,
}
}
// Settings is the default settings for http client
type Settings struct {
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TLSClientConfig *tls.Config
Transport http.RoundTripper
}
// Request provides more useful methods for requesting one url than http.Request.
type Request struct {
url string
req *http.Request
params map[string]string
setting Settings
resp *http.Response
body []byte
url string
req *http.Request
params map[string]string
readWriteTimeout time.Duration
transport http.RoundTripper
}
// SetContext sets the request's Context
@@ -63,36 +62,24 @@ func (r *Request) SetContext(ctx context.Context) *Request {
return r
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
func (r *Request) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *Request {
r.setting.ConnectTimeout = connectTimeout
r.setting.ReadWriteTimeout = readWriteTimeout
// SetTransport sets the request transport, if not set, will use httplib's default transport with environment proxy support
// ATTENTION: the http.Transport has a connection pool, so it should be reused as much as possible, do not create a lot of transports
func (r *Request) SetTransport(transport http.RoundTripper) *Request {
r.transport = transport
return r
}
func (r *Request) SetReadWriteTimeout(readWriteTimeout time.Duration) *Request {
r.setting.ReadWriteTimeout = readWriteTimeout
r.readWriteTimeout = readWriteTimeout
return r
}
// SetTLSClientConfig sets tls connection configurations if visiting https url.
func (r *Request) SetTLSClientConfig(config *tls.Config) *Request {
r.setting.TLSClientConfig = config
return r
}
// Header add header item string in request.
// Header set header item string in request.
func (r *Request) Header(key, value string) *Request {
r.req.Header.Set(key, value)
return r
}
// SetTransport sets transport to
func (r *Request) SetTransport(transport http.RoundTripper) *Request {
r.setting.Transport = transport
return r
}
// Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2...
func (r *Request) Param(key, value string) *Request {
@@ -125,11 +112,9 @@ func (r *Request) Body(data any) *Request {
return r
}
func (r *Request) getResponse() (*http.Response, error) {
if r.resp.StatusCode != 0 {
return r.resp, nil
}
// Response executes request client and returns the response.
// Caller MUST close the response body if no error occurs.
func (r *Request) Response() (*http.Response, error) {
var paramBody string
if len(r.params) > 0 {
var buf bytes.Buffer
@@ -160,59 +145,19 @@ func (r *Request) getResponse() (*http.Response, error) {
return nil, err
}
trans := r.setting.Transport
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: r.setting.TLSClientConfig,
Proxy: http.ProxyFromEnvironment,
DialContext: TimeoutDialer(r.setting.ConnectTimeout),
}
} else if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = r.setting.TLSClientConfig
}
if t.DialContext == nil {
t.DialContext = TimeoutDialer(r.setting.ConnectTimeout)
}
}
client := &http.Client{
Transport: trans,
Timeout: r.setting.ReadWriteTimeout,
Transport: r.transport,
Timeout: r.readWriteTimeout,
}
if client.Transport == nil {
client.Transport = defaultTransport()
}
if len(r.setting.UserAgent) > 0 && len(r.req.Header.Get("User-Agent")) == 0 {
r.req.Header.Set("User-Agent", r.setting.UserAgent)
if r.req.Header.Get("User-Agent") == "" {
r.req.Header.Set("User-Agent", "GiteaHttpLib")
}
resp, err := client.Do(r.req)
if err != nil {
return nil, err
}
r.resp = resp
return resp, nil
}
// Response executes request client gets response manually.
// Caller MUST close the response body if no error occurs
func (r *Request) Response() (*http.Response, error) {
if r == nil {
return nil, errors.New("invalid request")
}
return r.getResponse()
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr string) (c net.Conn, err error) {
return func(ctx context.Context, netw, addr string) (net.Conn, error) {
d := net.Dialer{Timeout: cTimeout}
conn, err := d.DialContext(ctx, netw, addr)
if err != nil {
return nil, err
}
return conn, nil
}
return client.Do(r.req)
}
func (r *Request) GoString() string {

View File

@@ -126,6 +126,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byt
// no sandbox attribute for pdf as it breaks rendering in at least safari. this
// should generally be safe as scripts inside PDF can not escape the PDF document
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
}

View File

@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
var (
@@ -166,12 +167,12 @@ func Init() {
log.Fatal("PID: %d Unable to initialize the bleve Repository Indexer at path: %s Error: %v", os.Getpid(), setting.Indexer.RepoPath, err)
}
case "elasticsearch":
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), setting.Indexer.RepoConnStr)
log.Info("PID: %d Initializing Repository Indexer at: %s", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
defer func() {
if err := recover(); err != nil {
log.Error("PANIC whilst initializing repository indexer: %v\nStacktrace: %s", err, log.Stack(2))
log.Error("The indexer files are likely corrupted and may need to be deleted")
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", setting.Indexer.RepoConnStr)
log.Error("You can completely remove the \"%s\" index to make Gitea recreate the indexes", util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr))
}
}()
@@ -181,7 +182,7 @@ func Init() {
cancel()
(*globalIndexer.Load()).Close()
close(waitChannel)
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), setting.Indexer.RepoConnStr, err)
log.Fatal("PID: %d Unable to initialize the elasticsearch Repository Indexer connstr: %s Error: %v", os.Getpid(), util.SanitizeCredentialURLs(setting.Indexer.RepoConnStr), err)
}
default:

View File

@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// IndexerMetadata is used to send data to the queue, so it contains only the ids.
@@ -100,7 +101,7 @@ func InitIssueIndexer(syncReindex bool) {
issueIndexer = elasticsearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueIndexerName)
existed, err = issueIndexer.Init(ctx)
if err != nil {
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
}
case "db":
issueIndexer = db.GetIndexer()
@@ -108,7 +109,7 @@ func InitIssueIndexer(syncReindex bool) {
issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName)
existed, err = issueIndexer.Init(ctx)
if err != nil {
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err)
log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", util.SanitizeCredentialURLs(setting.Indexer.IssueConnStr), err)
}
default:
log.Fatal("Unknown issue indexer type: %s", setting.Indexer.IssueType)

View File

@@ -5,7 +5,6 @@ package template
import (
"fmt"
"io"
"path"
"strconv"
@@ -76,7 +75,7 @@ func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTempla
}
defer r.Close()
content, err := io.ReadAll(r)
content, err := util.ReadWithLimit(r, 1024*1024)
if err != nil {
return nil, fmt.Errorf("read all: %w", err)
}

View File

@@ -157,7 +157,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
}
// Download implements transfer.Backend. The returned reader must be closed by the caller.
func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
func (g *GiteaBackend) Download(oid string, args transfer.Args) (_ io.ReadCloser, _ int64, retErr error) {
idMapStr, exists := args[argID]
if !exists {
return nil, 0, ErrMissingID
@@ -188,7 +188,15 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
if err != nil {
return nil, 0, fmt.Errorf("failed to get response: %w", err)
}
// no need to close the body here by "defer resp.Body.Close()", see below
// We must return the ReaderCloser but not "ReadAll", to avoid OOM.
// "transfer.Backend" will check io.Closer interface and close the Body reader.
// So only close the Body when error occurs
defer func() {
if retErr != nil {
_ = resp.Body.Close()
}
}()
if resp.StatusCode != http.StatusOK {
return nil, 0, statusCodeToErr(resp.StatusCode)
}
@@ -197,7 +205,6 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
if err != nil {
return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
}
// transfer.Backend will check io.Closer interface and close this Body reader
return resp.Body, respSize, nil
}

View File

@@ -15,6 +15,8 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"github.com/kballard/go-shellquote"
)
// RegisterRenderers registers all supported third part renderers according settings
@@ -56,14 +58,11 @@ func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return p.MarkupSanitizerRules
}
// SanitizerDisabled disabled sanitize if return true
func (p *Renderer) SanitizerDisabled() bool {
return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
}
// DisplayInIFrame represents whether render the content with an iframe
func (p *Renderer) DisplayInIFrame() bool {
return p.RenderContentMode == setting.RenderContentModeIframe
func (p *Renderer) GetExternalRendererOptions() (ret markup.ExternalRendererOptions) {
ret.SanitizerDisabled = p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
ret.DisplayInIframe = p.RenderContentMode == setting.RenderContentModeIframe
ret.ContentSandbox = p.RenderContentSandbox
return ret
}
func envMark(envName string) string {
@@ -81,7 +80,10 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
envMark("GITEA_PREFIX_SRC"), baseLinkSrc,
envMark("GITEA_PREFIX_RAW"), baseLinkRaw,
).Replace(p.Command)
commands := strings.Fields(command)
commands, err := shellquote.Split(command)
if err != nil || len(commands) == 0 {
return fmt.Errorf("%s invalid command %q: %w", p.Name(), p.Command, err)
}
args := commands[1:]
if p.IsInputFile {

View File

@@ -5,6 +5,7 @@ package markup
import (
"strings"
"unicode"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/setting"
@@ -66,26 +67,31 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
}
m[0] += start
m[1] += start
start = m[1]
alias := node.Data[m[0]:m[1]]
alias = strings.ReplaceAll(alias, ":", "")
converted := emoji.FromAlias(alias)
if converted == nil {
// check if this is a custom reaction
if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
replaceContent(node, m[0], m[1], createCustomEmoji(ctx, alias))
node = node.NextSibling.NextSibling
start = 0
continue
}
var nextChar byte
if m[1] < len(node.Data) {
nextChar = node.Data[m[1]]
}
if nextChar == ':' || unicode.IsLetter(rune(nextChar)) || unicode.IsDigit(rune(nextChar)) {
continue
}
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
node = node.NextSibling.NextSibling
start = 0
alias = strings.Trim(alias, ":")
converted := emoji.FromAlias(alias)
if converted != nil {
// standard emoji
replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
node = node.NextSibling.NextSibling
start = 0 // restart searching start since node has changed
} else if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
// custom reaction
replaceContent(node, m[0], m[1], createCustomEmoji(ctx, alias))
node = node.NextSibling.NextSibling
start = 0 // restart searching start since node has changed
}
}
}

View File

@@ -357,12 +357,9 @@ func TestRender_emoji(t *testing.T) {
`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">🤪</span><span class="emoji" aria-label="locked with key">🔐</span><span class="emoji" aria-label="money-mouth face">🤑</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
// should match nothing
test(
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
`<p>2001:0db8:85a3:0000:0000:8a2e:0370:7334</p>`)
test(
":not exist:",
`<p>:not exist:</p>`)
test(":100:200", `<p>:100:200</p>`)
test("std::thread::something", `<p>std::thread::something</p>`)
test(":not exist:", `<p>:not exist:</p>`)
}
func TestRender_ShortLinks(t *testing.T) {

View File

@@ -5,11 +5,13 @@ package internal
import (
"bytes"
"html/template"
"io"
)
type finalProcessor struct {
renderInternal *RenderInternal
extraHeadHTML template.HTML
output io.Writer
buf bytes.Buffer
@@ -25,6 +27,32 @@ func (p *finalProcessor) Close() error {
// because "postProcess" already does so. In the future we could optimize the code to process data on the fly.
buf := p.buf.Bytes()
buf = bytes.ReplaceAll(buf, []byte(` data-attr-class="`+p.renderInternal.secureIDPrefix), []byte(` class="`))
_, err := p.output.Write(buf)
tmp := bytes.TrimSpace(buf)
isLikelyHTML := len(tmp) != 0 && tmp[0] == '<' && tmp[len(tmp)-1] == '>' && bytes.Index(tmp, []byte(`</`)) > 0
if !isLikelyHTML {
// not HTML, write back directly
_, err := p.output.Write(buf)
return err
}
// add our extra head HTML into output
headBytes := []byte("<head>")
posHead := bytes.Index(buf, headBytes)
var part1, part2 []byte
if posHead >= 0 {
part1, part2 = buf[:posHead+len(headBytes)], buf[posHead+len(headBytes):]
} else {
part1, part2 = nil, buf
}
if len(part1) > 0 {
if _, err := p.output.Write(part1); err != nil {
return err
}
}
if _, err := io.WriteString(p.output, string(p.extraHeadHTML)); err != nil {
return err
}
_, err := p.output.Write(part2)
return err
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestRenderInternal(t *testing.T) {
func TestRenderInternalAttrs(t *testing.T) {
cases := []struct {
input, protected, recovered string
}{
@@ -30,7 +30,7 @@ func TestRenderInternal(t *testing.T) {
for _, c := range cases {
var r RenderInternal
out := &bytes.Buffer{}
in := r.init("sec", out)
in := r.init("sec", out, "")
protected := r.ProtectSafeAttrs(template.HTML(c.input))
assert.EqualValues(t, c.protected, protected)
_, _ = io.WriteString(in, string(protected))
@@ -41,7 +41,7 @@ func TestRenderInternal(t *testing.T) {
var r1, r2 RenderInternal
protected := r1.ProtectSafeAttrs(`<div class="test"></div>`)
assert.EqualValues(t, `<div class="test"></div>`, protected, "non-initialized RenderInternal should not protect any attributes")
_ = r1.init("sec", nil)
_ = r1.init("sec", nil, "")
protected = r1.ProtectSafeAttrs(`<div class="test"></div>`)
assert.EqualValues(t, `<div data-attr-class="sec:test"></div>`, protected)
assert.Equal(t, "data-attr-class", r1.SafeAttr("class"))
@@ -54,8 +54,37 @@ func TestRenderInternal(t *testing.T) {
assert.Empty(t, recovered)
out2 := &bytes.Buffer{}
in2 := r2.init("sec-other", out2)
in2 := r2.init("sec-other", out2, "")
_, _ = io.WriteString(in2, string(protected))
_ = in2.Close()
assert.Equal(t, `<div data-attr-class="sec:test"></div>`, out2.String(), "different secureID should not recover the value")
}
func TestRenderInternalExtraHead(t *testing.T) {
t.Run("HeadExists", func(t *testing.T) {
out := &bytes.Buffer{}
var r RenderInternal
in := r.init("sec", out, `<MY-TAG>`)
_, _ = io.WriteString(in, `<head>any</head>`)
_ = in.Close()
assert.Equal(t, `<head><MY-TAG>any</head>`, out.String())
})
t.Run("HeadNotExists", func(t *testing.T) {
out := &bytes.Buffer{}
var r RenderInternal
in := r.init("sec", out, `<MY-TAG>`)
_, _ = io.WriteString(in, `<div></div>`)
_ = in.Close()
assert.Equal(t, `<MY-TAG><div></div>`, out.String())
})
t.Run("NotHTML", func(t *testing.T) {
out := &bytes.Buffer{}
var r RenderInternal
in := r.init("sec", out, `<MY-TAG>`)
_, _ = io.WriteString(in, `<any>`)
_ = in.Close()
assert.Equal(t, `<any>`, out.String())
})
}

View File

@@ -29,19 +29,19 @@ type RenderInternal struct {
secureIDPrefix string
}
func (r *RenderInternal) Init(output io.Writer) io.WriteCloser {
func (r *RenderInternal) Init(output io.Writer, extraHeadHTML template.HTML) io.WriteCloser {
buf := make([]byte, 12)
_, err := rand.Read(buf)
if err != nil {
panic("unable to generate secure id")
}
return r.init(base64.URLEncoding.EncodeToString(buf), output)
return r.init(base64.URLEncoding.EncodeToString(buf), output, extraHeadHTML)
}
func (r *RenderInternal) init(secID string, output io.Writer) io.WriteCloser {
func (r *RenderInternal) init(secID string, output io.Writer, extraHeadHTML template.HTML) io.WriteCloser {
r.secureID = secID
r.secureIDPrefix = r.secureID + ":"
return &finalProcessor{renderInternal: r, output: output}
return &finalProcessor{renderInternal: r, output: output, extraHeadHTML: extraHeadHTML}
}
func (r *RenderInternal) RecoverProtectedValue(v string) (string, bool) {

View File

@@ -30,6 +30,10 @@ func TestMathRender(t *testing.T) {
"$ a $",
`<p><code class="language-math">a</code></p>` + nl,
},
{
"$a$$b$",
`<p><code class="language-math">a</code><code class="language-math">b</code></p>` + nl,
},
{
"$a$ $b$",
`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
@@ -59,7 +63,7 @@ func TestMathRender(t *testing.T) {
`<p>a$b $a a$b b$</p>` + nl,
},
{
"a$x$",
"a$x$", // Pattern: "word$other$" The real world example is: "Price is between US$1 and US$2.", so don't parse this.
`<p>a$x$</p>` + nl,
},
{
@@ -70,6 +74,10 @@ func TestMathRender(t *testing.T) {
"$a$ ($b$) [$c$] {$d$}",
`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
},
{
"[$a$](link)",
`<p><a href="/link" rel="nofollow"><code class="language-math">a</code></a></p>` + nl,
},
{
"$$a$$",
`<p><code class="language-math">a</code></p>` + nl,

View File

@@ -54,6 +54,10 @@ func isAlphanumeric(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
}
func isInMarkdownLinkText(block text.Reader, lineAfter []byte) bool {
return block.PrecendingCharacter() == '[' && bytes.HasPrefix(lineAfter, []byte("]("))
}
// Parse parses the current line and returns a result of parsing.
func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
@@ -115,7 +119,9 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
}
// check valid ending character
isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 ||
succeedingCharacter == '$' ||
isInMarkdownLinkText(block, line[i+len(stopMark):])
if checkSurrounding && !isValidEndingChar {
break
}

View File

@@ -6,12 +6,14 @@ package markup
import (
"context"
"fmt"
"html/template"
"io"
"net/url"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/markup/internal"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
@@ -120,31 +122,38 @@ func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext {
return ctx
}
// Render renders markup file to HTML with all specific handling stuff.
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
// FindRendererByContext finds renderer by RenderContext
// TODO: it should be merged with other similar functions like GetRendererByFileName, DetectMarkupTypeByFileName, etc
func FindRendererByContext(ctx *RenderContext) (Renderer, error) {
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
if ctx.RenderOptions.MarkupType == "" {
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
return nil, util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
}
}
renderer := renderers[ctx.RenderOptions.MarkupType]
if renderer == nil {
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
return nil, util.NewNotExistErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
}
if ctx.RenderOptions.RelativePath != "" {
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
if !ctx.RenderOptions.InStandalonePage {
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
// otherwise, a <iframe> should be outputted to embed the external rendered page
return renderIFrame(ctx, output)
}
}
}
return renderer, nil
}
return render(ctx, renderer, input, output)
func RendererNeedPostProcess(renderer Renderer) bool {
if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() {
return true
}
return false
}
// Render renders markup file to HTML with all specific handling stuff.
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
renderer, err := FindRendererByContext(ctx)
if err != nil {
return err
}
return RenderWithRenderer(ctx, renderer, input, output)
}
// RenderString renders Markup string to HTML with all specific handling stuff and return string
@@ -156,24 +165,20 @@ func RenderString(ctx *RenderContext, content string) (string, error) {
return buf.String(), nil
}
func renderIFrame(ctx *RenderContext, output io.Writer) error {
// set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight)
// at the moment, only "allow-scripts" is allowed for sandbox mode.
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
// TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read
_, err := io.WriteString(output, fmt.Sprintf(`
<iframe src="%s/%s/%s/render/%s/%s"
name="giteaExternalRender"
onload="this.height=giteaExternalRender.document.documentElement.scrollHeight"
width="100%%" height="0" scrolling="no" frameborder="0" style="overflow: hidden"
sandbox="allow-scripts"
></iframe>`,
setting.AppSubURL,
func renderIFrame(ctx *RenderContext, sandbox string, output io.Writer) error {
src := fmt.Sprintf("%s/%s/%s/render/%s/%s", setting.AppSubURL,
url.PathEscape(ctx.RenderOptions.Metas["user"]),
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
url.PathEscape(ctx.RenderOptions.RelativePath),
))
util.PathEscapeSegments(ctx.RenderOptions.Metas["RefTypeNameSubURL"]),
util.PathEscapeSegments(ctx.RenderOptions.RelativePath),
)
var sandboxAttrValue template.HTML
if sandbox != "" {
sandboxAttrValue = htmlutil.HTMLFormat(`sandbox="%s"`, sandbox)
}
iframe := htmlutil.HTMLFormat(`<iframe data-src="%s" class="external-render-iframe" %s></iframe>`, src, sandboxAttrValue)
_, err := io.WriteString(output, string(iframe))
return err
}
@@ -185,13 +190,34 @@ func pipes() (io.ReadCloser, io.WriteCloser, func()) {
}
}
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
func getExternalRendererOptions(renderer Renderer) (ret ExternalRendererOptions, _ bool) {
if externalRender, ok := renderer.(ExternalRenderer); ok {
return externalRender.GetExternalRendererOptions(), true
}
return ret, false
}
func RenderWithRenderer(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
var extraHeadHTML template.HTML
if extOpts, ok := getExternalRendererOptions(renderer); ok && extOpts.DisplayInIframe {
if !ctx.RenderOptions.InStandalonePage {
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
// otherwise, a <iframe> should be outputted to embed the external rendered page
return renderIFrame(ctx, extOpts.ContentSandbox, output)
}
// else: this is a standalone page, fallthrough to the real rendering, and add extra JS/CSS
extraStyleHref := setting.AppSubURL + "/assets/css/external-render-iframe.css"
extraScriptSrc := setting.AppSubURL + "/assets/js/external-render-iframe.js"
// "<script>" must go before "<link>", to make Golang's http.DetectContentType() can still recognize the content as "text/html"
extraHeadHTML = htmlutil.HTMLFormat(`<script src="%s"></script><link rel="stylesheet" href="%s">`, extraScriptSrc, extraStyleHref)
}
ctx.usedByRender = true
if ctx.RenderHelper != nil {
defer ctx.RenderHelper.CleanUp()
}
finalProcessor := ctx.RenderInternal.Init(output)
finalProcessor := ctx.RenderInternal.Init(output, extraHeadHTML)
defer finalProcessor.Close()
// input -> (pw1=pr1) -> renderer -> (pw2=pr2) -> SanitizeReader -> finalProcessor -> output
@@ -202,7 +228,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
eg, _ := errgroup.WithContext(ctx)
var pw2 io.WriteCloser = util.NopCloser{Writer: finalProcessor}
if r, ok := renderer.(ExternalRenderer); !ok || !r.SanitizerDisabled() {
if r, ok := renderer.(ExternalRenderer); !ok || !r.GetExternalRendererOptions().SanitizerDisabled {
var pr2 io.ReadCloser
var close2 func()
pr2, pw2, close2 = pipes()
@@ -214,7 +240,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
}
eg.Go(func() (err error) {
if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() {
if RendererNeedPostProcess(renderer) {
err = PostProcessDefault(ctx, pr1, pw2)
} else {
_, err = io.Copy(pw2, pr1)

View File

@@ -25,13 +25,15 @@ type PostProcessRenderer interface {
NeedPostProcess() bool
}
type ExternalRendererOptions struct {
SanitizerDisabled bool
DisplayInIframe bool
ContentSandbox string
}
// ExternalRenderer defines an interface for external renderers
type ExternalRenderer interface {
// SanitizerDisabled disabled sanitize if return true
SanitizerDisabled() bool
// DisplayInIFrame represents whether render the content with an iframe
DisplayInIFrame() bool
GetExternalRendererOptions() ExternalRendererOptions
}
// RendererContentDetector detects if the content can be rendered

View File

@@ -30,6 +30,9 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
// Chroma always uses 1-2 letters for style names, we could tolerate it at the moment
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^\w{0,2}$`)).OnElements("span")
// Line numbers on codepreview
policy.AllowAttrs("data-line-number").OnElements("span")
// Custom URL-Schemes
if len(setting.Markdown.CustomURLSchemes) > 0 {
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)

View File

@@ -46,7 +46,7 @@ var (
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#source
namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`)
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
)
type Package struct {

View File

@@ -176,4 +176,12 @@ func TestParseControlFile(t *testing.T) {
assert.Equal(t, []string{"a", "b"}, p.Metadata.Dependencies)
assert.Equal(t, full, p.Control)
})
t.Run("ValidVersions", func(t *testing.T) {
for _, version := range []string{"1.0", "0:1.2", "9:1.0", "10:1.0", "900:1a.2b-x-y_z~1+2"} {
p, err := ParseControlFile(buildContent("testpkg", version, "amd64"))
assert.NoError(t, err, "ParseControlFile with version %q", version)
assert.NotNil(t, p)
}
})
}

View File

@@ -62,7 +62,28 @@ type PackageMetadata struct {
Author User `json:"author"`
ReadmeFilename string `json:"readmeFilename,omitempty"`
Users map[string]bool `json:"users,omitempty"`
License string `json:"license,omitempty"`
License License `json:"license,omitempty"`
}
type License string
func (l *License) UnmarshalJSON(data []byte) error {
switch data[0] {
case '"':
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
*l = License(value)
case '{':
var values map[string]any
if err := json.Unmarshal(data, &values); err != nil {
return err
}
value, _ := values["type"].(string)
*l = License(value)
}
return nil
}
// PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
@@ -74,7 +95,7 @@ type PackageMetadataVersion struct {
Description string `json:"description"`
Author User `json:"author"`
Homepage string `json:"homepage,omitempty"`
License string `json:"license,omitempty"`
License License `json:"license,omitempty"`
Repository Repository `json:"repository"`
Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`

View File

@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParsePackage(t *testing.T) {
@@ -291,11 +292,36 @@ func TestParsePackage(t *testing.T) {
assert.Equal(t, packageDescription, p.Metadata.Readme)
assert.Equal(t, packageAuthor, p.Metadata.Author)
assert.Equal(t, packageBin, p.Metadata.Bin["bin"])
assert.Equal(t, "MIT", p.Metadata.License)
assert.Equal(t, "MIT", string(p.Metadata.License))
assert.Equal(t, "https://gitea.io/", p.Metadata.ProjectURL)
assert.Contains(t, p.Metadata.Dependencies, "package")
assert.Equal(t, "1.2.0", p.Metadata.Dependencies["package"])
assert.Equal(t, repository.Type, p.Metadata.Repository.Type)
assert.Equal(t, repository.URL, p.Metadata.Repository.URL)
})
t.Run("ValidLicenseMap", func(t *testing.T) {
packageJSON := `{
"versions": {
"0.1.1": {
"name": "dev-null",
"version": "0.1.1",
"license": {
"type": "MIT"
},
"dist": {
"integrity": "sha256-"
}
}
},
"_attachments": {
"foo": {
"data": "AAAA"
}
}
}`
p, err := ParsePackage(strings.NewReader(packageJSON))
require.NoError(t, err)
require.Equal(t, "MIT", string(p.Metadata.License))
})
}

View File

@@ -12,7 +12,7 @@ type Metadata struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
License string `json:"license,omitempty"`
License License `json:"license,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`

View File

@@ -216,7 +216,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme)
if err == nil {
buf, _ := io.ReadAll(f)
buf, _ := util.ReadWithLimit(f, 1024*1024)
m.Readme = string(buf)
_ = f.Close()
}

View File

@@ -89,7 +89,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
return nil, err
}
} else if strings.EqualFold(hd.Name, "readme.md") {
data, err := io.ReadAll(tr)
data, err := util.ReadWithLimit(tr, 1024*1024)
if err != nil {
return nil, err
}

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"os"
"strings"
"sync"
"time"
"code.gitea.io/gitea/modules/httplib"
@@ -33,6 +34,35 @@ func getClientIP() string {
return strings.Fields(sshConnEnv)[0]
}
func dialContextInternalAPI(ctx context.Context, network, address string) (conn net.Conn, err error) {
d := net.Dialer{Timeout: 10 * time.Second}
if setting.Protocol == setting.HTTPUnix {
conn, err = d.DialContext(ctx, "unix", setting.HTTPAddr)
} else {
conn, err = d.DialContext(ctx, network, address)
}
if err != nil {
return nil, err
}
if setting.LocalUseProxyProtocol {
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
_ = conn.Close()
return nil, err
}
}
return conn, nil
}
var internalAPITransport = sync.OnceValue(func() http.RoundTripper {
return &http.Transport{
DialContext: dialContextInternalAPI,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: setting.Domain,
},
}
})
func NewInternalRequest(ctx context.Context, url, method string) *httplib.Request {
if setting.InternalToken == "" {
log.Fatal(`The INTERNAL_TOKEN setting is missing from the configuration file: %q.
@@ -43,49 +73,11 @@ Ensure you are running in the correct environment or set the correct configurati
log.Fatal("Invalid internal request URL: %q", url)
}
req := httplib.NewRequest(url, method).
return httplib.NewRequest(url, method).
SetContext(ctx).
SetTransport(internalAPITransport()).
Header("X-Real-IP", getClientIP()).
Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken).
SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
ServerName: setting.Domain,
})
if setting.Protocol == setting.HTTPUnix {
req.SetTransport(&http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
if err != nil {
return conn, err
}
if setting.LocalUseProxyProtocol {
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
_ = conn.Close()
return nil, err
}
}
return conn, err
},
})
} else if setting.LocalUseProxyProtocol {
req.SetTransport(&http.Transport{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
var d net.Dialer
conn, err := d.DialContext(ctx, network, address)
if err != nil {
return conn, err
}
if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
_ = conn.Close()
return nil, err
}
return conn, err
},
})
}
return req
Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken)
}
func newInternalRequestAPI(ctx context.Context, url, method string, body ...any) *httplib.Request {
@@ -98,6 +90,6 @@ func newInternalRequestAPI(ctx context.Context, url, method string, body ...any)
log.Fatal("Too many arguments for newInternalRequestAPI")
}
req.SetTimeout(10*time.Second, 60*time.Second)
req.SetReadWriteTimeout(60 * time.Second)
return req
}

View File

@@ -6,7 +6,6 @@ package private
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/modules/setting"
)
@@ -31,6 +30,6 @@ func RestoreRepo(ctx context.Context, repoDir, ownerName, repoName string, units
Units: units,
Validation: validation,
})
req.SetTimeout(3*time.Second, 0) // since the request will spend much time, don't timeout
req.SetReadWriteTimeout(0) // since the request will spend much time, don't timeout
return requestJSONClientMsg(req, fmt.Sprintf("Restore repo %s/%s successfully", ownerName, repoName))
}

View File

@@ -16,9 +16,13 @@ var Attachment AttachmentSettingType
func loadAttachmentFrom(rootCfg ConfigProvider) (err error) {
Attachment = AttachmentSettingType{
AllowedTypes: ".avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip",
MaxSize: 2048,
MaxFiles: 5,
Enabled: true,
// FIXME: this size is used for both "issue attachment" and "release attachment"
// The design is not right, these two should be different settings
MaxSize: 2048,
MaxFiles: 5,
Enabled: true,
}
sec, _ := rootCfg.GetSection("attachment")
if sec == nil {

View File

@@ -202,11 +202,11 @@ func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
loadedFromEmpty := true
if file != "" {
isFile, err := util.IsFile(file)
isExist, err := util.IsExist(file)
if err != nil {
return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
return nil, fmt.Errorf("unable to check if %q exists: %v", file, err)
}
if isFile {
if isExist {
if err = cfg.Append(file); err != nil {
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
}
@@ -327,14 +327,14 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
}
}
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
if rootCfg.Section(oldSection).HasKey(oldKey) {
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
}
}

View File

@@ -63,6 +63,7 @@ type MarkupRenderer struct {
NeedPostProcess bool
MarkupSanitizerRules []MarkupSanitizerRule
RenderContentMode string
RenderContentSandbox string
}
// MarkupSanitizerRule defines the policy for whitelisting attributes on
@@ -253,13 +254,24 @@ func newMarkupRenderer(name string, sec ConfigSection) {
renderContentMode = RenderContentModeSanitized
}
// ATTENTION! at the moment, only a safe set like "allow-scripts" are allowed for sandbox mode.
// "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token
renderContentSandbox := sec.Key("RENDER_CONTENT_SANDBOX").MustString("allow-scripts allow-popups")
if renderContentSandbox == "disabled" {
renderContentSandbox = ""
}
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
Enabled: sec.Key("ENABLED").MustBool(false),
MarkupName: name,
FileExtensions: exts,
Command: command,
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
RenderContentMode: renderContentMode,
Enabled: sec.Key("ENABLED").MustBool(false),
MarkupName: name,
FileExtensions: exts,
Command: command,
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
RenderContentMode: renderContentMode,
RenderContentSandbox: renderContentSandbox,
// if no sanitizer is needed, no post process is needed
NeedPostProcess: sec.Key("NEED_POST_PROCESS").MustBool(renderContentMode == RenderContentModeSanitized),
})
}

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