Compare commits

...

85 Commits

Author SHA1 Message Date
Giteabot
cccd54999a Fix releases sidebar navigation link (#34436) (#34439)
Backport #34436 by @badhezi

Resolves https://github.com/go-gitea/gitea/issues/34435

---------

Co-authored-by: badhezi <zlilaharon@gmail.com>
2025-05-12 22:01:45 +00:00
Lunny Xiao
3e7cb5b639 Add changelog for 1.23.8 (#34430)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-05-12 11:47:43 -07:00
Giteabot
ec146b4200 Fix bug webhook milestone is not right. (#34419) (#34429)
Backport #34419 by @lunny

Fix #34400

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2025-05-12 15:26:31 +02:00
wxiaoguang
51fa86f0fa Fix a bug when uploading file via lfs ssh command 1.23 (#34408) (#34411)
Partially backport #34408
2025-05-09 20:42:59 +08:00
Lunny Xiao
e67b004535 upgrade github v61 -> v71 to fix migrating bug (#34389)
backport #34385
2025-05-07 14:42:30 -04:00
Giteabot
a4cc867401 Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
Backport #34333 by @lunny

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-05-05 10:04:52 -07:00
Giteabot
8e5aa8fb1e Fix bug when visiting comparation page (#34334) (#34364)
Backport #34334 by @lunny

The `ci.HeadGitRepo` was opened and closed in the function
`ParseCompareInfo` but reused in the function `PrepareCompareDiff`.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-05-04 19:56:47 +00:00
NorthRealm
046fc8684c Fix CI Build (#34315)
Backport  #34309
2025-04-29 13:11:08 -07:00
Giteabot
e3e705200a Fix wrong review requests when updating the pull request (#34286) (#34304)
Backport #34286 by @lunny

Fix #34224

The previous implementation in #33744 will get the pushed commits
changed files. But it's not always right when push a merged commit. This
PR reverted the logic in #33744 and will always get the PR's changed
files and get code owners.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-28 19:48:45 -07:00
Giteabot
a9d5ab8f88 fix github migration error when using multiple tokens (#34144) (#34302)
Backport #34144 by @TheFox0x7

Git authorization was not taking into account multiple token feature,
leading to auth failures

Closes: https://github.com/go-gitea/gitea/issues/34141

---------

Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-28 17:12:36 +00:00
Lunny Xiao
5546b4279c Explicitly not update indexes when sync database schemas (#34281) (#34295)
Fix #34275
Backport #34281

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-04-28 16:30:42 +00:00
Giteabot
4f6d09fb68 Update token creation API swagger documentation (#34288) (#34296)
Backport #34288 by @lunny

Fix #34231

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-27 19:34:46 -07:00
Lunny Xiao
4e5aca62ee Fix panic when comment is nil (#34257) (#34277)
Fix #34254
Backport #34257
2025-04-24 18:28:02 -07:00
Giteabot
030ed9462d Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
Backport #34244 by kemzeb

Resolves #34218.

In the recent past, the default wiki branch was made to be changeable.
This change reflects this.

Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
2025-04-19 03:37:22 +00:00
Giteabot
60f175f7ff Swift files can be passed either as file or as form value (#34068) (#34236)
Backport #34068 by wgr1984

Fix #33990

Co-authored-by: Wolfgang Reithmeier <w.reithmeier@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-04-18 12:34:28 +00:00
Giteabot
260816d64a Fix project board links to related Pull Requests (#34213) (#34222)
Backport #34213 by @badhezi

Resolves https://github.com/go-gitea/gitea/issues/34181

Co-authored-by: badhezi <zlilaharon@gmail.com>
2025-04-17 20:10:58 -07:00
Lunny Xiao
a01f56a61a Update net package (#34228) (#34232) 2025-04-18 09:45:59 +08:00
Giteabot
c01459a504 Fix two missed null value checks on the wiki page. (#34205) (#34215)
Backport #34205 by @kerwin612

before:

![image](https://github.com/user-attachments/assets/83e5513f-a4fa-406d-a010-8ec8cd873203)

after:

![image](https://github.com/user-attachments/assets/6bca76c7-0445-429a-92b1-1a9f96d6daca)

Co-authored-by: Kerwin Bryant <kerwin612@qq.com>
2025-04-16 09:23:55 -07:00
Giteabot
6e4e3ca012 Fix empty repo clone panel border (#34219) (#34220)
Backport #34219 by kerwin612

Co-authored-by: Kerwin Bryant <kerwin612@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-04-16 15:46:33 +08:00
Giteabot
ed0e8865f3 feat: add riscv64 support (#34199) (#34204)
Backport #34199 by @mengzhuo

Co-authored-by: Meng Zhuo <mengzhuo1203@gmail.com>
2025-04-14 16:57:43 +00:00
Giteabot
dc5adce70d fix(#33711): cross-publish docker images to ghcr.io (#34148) (#34176) 2025-04-10 21:56:06 -04:00
Giteabot
328ce0485f bugfix check for alternate ssh host certificate location (#34146) (#34166)
Backport #34146 by ManInDark

fixes #34145

Edited all locations to actually be correct.

Co-authored-by: ManInDark <61268856+ManInDark@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-04-10 12:00:43 +08:00
Giteabot
39b6abf955 remove hardcoded 'code' string in clone_panel.tmpl (#34153) (#34158)
Backport #34153 by @wkelly17

This commit replaces the hardcoded string "code" in the clone panel
button with the i18n local for repo.code.

Co-authored-by: Will Kelly <67284402+wkelly17@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
2025-04-09 12:14:28 +02:00
silverwind
3eda79647b Bump go version in go.mod (#34160)
Fixes: https://pkg.go.dev/vuln/GO-2025-3563

Because of a bug in go, we can not remove the minor version in go.mod
without `make tidy` introducing a potentially harmful `toolchain`
directive, so just bump the go version here. From what I gather, this
will be fixed in go 1.25.
2025-04-09 10:57:54 +02:00
Lunny Xiao
97171be1b4 Release note 1.23.7 (#34117) 2025-04-07 18:31:28 +00:00
wxiaoguang
9ef2a338d8 Fix block expensive for 1.23 (#34127) 2025-04-06 21:50:37 +08:00
Giteabot
66ccae8b90 Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
Backport #34084 by @Mopcho

Fixes [#34027](https://github.com/go-gitea/gitea/issues/34027)

Discord does not allow for description bigger than 2048 bytes. If the
description is bigger than that it will throw 400 and the event won't
appear in discord. To fix that, in the createPayload method we now slice
the description to ensure it doesn’t exceed the limit.

---------

Co-authored-by: Mopcho <47301161+Mopcho@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-05 02:08:04 +00:00
Zettat123
0e6489317e Get changed files based on merge base when checking pull_request actions trigger (#34106) (#34120)
Backport #34106

Fix #33941
2025-04-03 22:10:54 -07:00
Giteabot
67dc1ff926 Fix invalid version in RPM package path (#34112) (#34115)
Backport #34112 by @KN4CK3R

Fixes #34017

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2025-04-03 21:18:46 +00:00
Giteabot
4ee4c06b07 also check default ssh-cert location for host (#34099) (#34100) (#34116)
Backport #34100 by @ManInDark

Fixes #34099.

Resolved by checking the `key-cert.pub` location alongside the
previously configured location. In case a certificate is already found,
this won't change anything, but if there is one in `key-cert.pub` but
not in `key_cert`, it'll use that one now.

Co-authored-by: ManInDark <61268856+ManInDark@users.noreply.github.com>
2025-04-04 04:38:24 +08:00
wxiaoguang
3063e37802 Fix markdown frontmatter rendering (#34102) (#34107)
Backport #34102
Fix #34101
2025-04-03 15:26:43 +08:00
wxiaoguang
a40e15a116 Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
Backport #34080

Co-authored-by: Kemal Zebari <60799661+kemzeb@users.noreply.github.com>
2025-04-03 01:05:28 +08:00
wxiaoguang
8f75f61b64 Do not show 500 error when default branch doesn't exist (#34096) (#34097)
Backport #34096
2025-04-02 18:16:41 +08:00
Giteabot
25e409e025 Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
Backport #34094 by @lunny

When visit commit list, it would update the user avatar even if id = 0,
which was unnecessary operations. This PR returned default avatar for
the git only user avatar rendering who's user id is zero.

```log
database duration=0.0005s db.sql="UPDATE `user` SET `avatar` = ?, `updated_unix` = ? WHERE `id`=?"
database duration=0.0007s db.sql="UPDATE `user` SET `avatar` = ?, `updated_unix` = ? WHERE `id`=?"
...
```

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-04-02 09:37:17 +08:00
Lunny Xiao
f8f24d83cf Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
Backport #34053 

When a team have no code unit permission of a repository, the member of
the team should not view activity contributors, recent commits and code
frequrency.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-30 18:30:55 +08:00
wxiaoguang
15e93a751c Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
Backport #34024 since there are too many AI crawlers. The new code is
covered by tests and it does nothing if users don't set it.
2025-03-30 06:16:32 +00:00
Giteabot
5a9b3bfa50 add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
Backport #34061 by eeyrjmr

The doctor storage check reconstructs the lfs oid by producing a string
where the path separator is stripped
ab/dc/efg -> abdcefg. Windows however uses a backslash and thus the
ReplaceAll call doesn't produce the correct oid resulting in all lfs
objects being classed as orphaned.
This PR allows this to be more OS agnostic.

Closes #34039

Co-authored-by: JonRB <4564448+eeyrjmr@users.noreply.github.com>
2025-03-30 05:51:08 +00:00
wxiaoguang
dd901983c0 Fix repo-template.ts error in 1.23 (#34060)
Fix #34059
2025-03-29 12:30:27 -07:00
Lunny Xiao
7f962a16c9 Pull request updates will also trigger code owners review requests (#33744) (#34045)
Fix #33490
Backport #33744 

It will only read the changed file on the pushed commits but not all the
files of this PR.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-28 17:24:31 +00:00
wxiaoguang
5d81f6d73f Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
And fix layout for mobile

Backport #33667 

Fix #33880

---------

Co-authored-by: Kerwin Bryant <kerwin612@qq.com>
2025-03-28 12:37:27 +00:00
Giteabot
eee4a752a5 Simplify emoji rendering (#34048) (#34049)
Backport #34048 by silverwind

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-28 10:30:29 +00:00
wxiaoguang
b3516767fb fix org repo creation being limited by user limits (#34030) (#34044)
Backport #34030
2025-03-28 07:59:46 +00:00
wxiaoguang
8d1be2a9c5 Fix git client accessing renamed repo (#34034) (#34043)
Backport #34034
2025-03-28 14:59:46 +08:00
Giteabot
e46f9ff534 Fix the issue with error message logging for the check-attr command on Windows OS. (#34035) (#34036)
Backport #34035 by charles7668

Close #34022 , #33550 

This error message always appears when using the `check-attr` command,
even though it works correctly.
The issue occurs when the stdin writer is closed, so I added a special
case to handle and check the error message when the exit code is 1.

Co-authored-by: charles <30816317+charles7668@users.noreply.github.com>
2025-03-27 09:50:57 +00:00
Giteabot
690e810bcc Try to fix check-attr bug (#34029) (#34033)
Backport #34029 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-27 05:36:07 +00:00
Giteabot
35983ac0a8 Polyfill WeakRef (#34025) (#34028)
Backport #34025 by wxiaoguang

Fix #33407

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-27 00:13:22 +08:00
Giteabot
4b3400bd9c Git client will follow 301 but 307 (#34005) (#34010)
Backport #34005 by lunny

Fix #28460

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-25 15:56:44 +08:00
Lunny Xiao
7758df4264 Add changelog for 1.23.6 (#33975) (#34000)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2025-03-24 13:08:00 -07:00
Giteabot
f994f3cac6 Fix incorrect code search indexer options (#33992) (#33999)
Backport #33992 by @wxiaoguang

Fix #33798

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-24 10:02:05 -07:00
wxiaoguang
f514b2651e Update golang crypto and net for 1.23 (#33989) 2025-03-24 01:18:29 +08:00
TheFox0x7
347101f2a8 update jwt and redis packages (#33984) (#33987) 2025-03-23 15:35:27 +00:00
Giteabot
b5f8c4a510 Drop timeout for requests made to the internal hook api (#33947) (#33970)
Backport #33947 by Mik4sa

Co-authored-by: Kai Leonhardt <8343141+Mik4sa@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-22 08:46:54 +08:00
wxiaoguang
d6cee7c596 Fix oauth2 auth (#33961) (#33962)
Backport #33961 

UI fix is not needed.
2025-03-21 20:50:44 +08:00
wxiaoguang
987219ab3c Fix incorrect 1.23 translations (#33932)
Fix #33931
2025-03-18 11:13:14 -04:00
wxiaoguang
92280637a4 Try to figure out attribute checker problem (#33901) (#33902)
Backport #33901
2025-03-17 11:59:51 -07:00
Giteabot
5fadcf997e Fix maven panic when no package exists (#33888) (#33889)
Backport #33888 by @wxiaoguang

Fix #33886

Restore the old logic from #16510, which was incorrectly removed by
#33678

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-14 11:11:41 -07:00
Giteabot
be94f7bc07 Ignore trivial errors when updating push data (#33864) (#33887)
Backport #33864 by wxiaoguang

Fix #23213

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-14 15:37:00 +00:00
Giteabot
9054a6670c Fix markdown render (#33870) (#33875)
Backport #33870 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-14 00:28:10 +00:00
ChristopherHX
fc82204fca Fix auto concurrency cancellation skips commit status updates (#33764) (#33849)
Backport #33764
* add missing commit status
* conflicts with concurrency support
2025-03-11 16:51:58 +00:00
wxiaoguang
6f8e62fa9c Fix some UI problems for 1.23 (#33856)
Partially backport #32927 #33851
2025-03-11 23:16:33 +08:00
Giteabot
a2c6ecc093 Fix LFS URL (#33840) (#33843)
Backport #33840 by wxiaoguang

Fix #33839

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-03-10 11:26:31 +01:00
Giteabot
523a84e5d0 Removing unwanted ui container (#33833) (#33835)
Backport #33833 by Vinoth-kumar-Ganesan

when the passkey auth and register was disabled
the unwanted ui container was show

Co-authored-by: Vinoth Kumar <103478407+Vinoth-kumar-Ganesan@users.noreply.github.com>
Co-authored-by: Vinoth414 <103478407+Vinoth414@users.noreply.github.com>
2025-03-09 17:02:45 +00:00
wxiaoguang
869ee4fc38 Do not call "git diff" when listing PRs (#33817)
Fix  #31492
2025-03-08 07:41:51 +00:00
wxiaoguang
16a332464d Try to fix ACME (3rd) (#33807) (#33808)
Backport #33807
2025-03-08 15:16:54 +08:00
wxiaoguang
d03e7fd65e Support disable passkey auth (#33348) (#33819)
* Backport #33348
* Backport #33820

---------

Co-authored-by: yp05327 <576951401@qq.com>
2025-03-07 21:31:25 +02:00
Lunny Xiao
92f2d904f0 Upgrade golang net from 0.35.0 -> 0.36.0 #33795 (#33796)
Backport #33795
2025-03-04 15:39:08 -08:00
Lunny Xiao
9c3511f0b1 Add changelog for 1.23.5 (#33780)
Wait tomorrow's Golang version.
Maybe wait backport of #33764 and #33744, #33789

---------

Co-authored-by: metiftikci <metiftikci@hotmail.com>
2025-03-04 20:49:46 +00:00
Giteabot
69d35ee911 Adjust appearence of commit status webhook (#33778) (#33789)
Backport #33778 by @denyskon

Some visual improvement for the commit status webhook message introduced
by #33320

- use short commit SHA as already done in e. g. commit webhook
- fix spacing, link text
- do not set user link for internal gitea-actions user

Before: 

![grafik](https://github.com/user-attachments/assets/9c460846-c350-444c-89b5-8a0d5e26cb86)

After:

![grafik](https://github.com/user-attachments/assets/05519cd8-6d8f-432b-bd9d-082de558a55a)

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2025-03-04 12:47:05 -08:00
techknowlogick
b8a7c20474 Update minimum version of mssql tested 2025-03-04 15:09:07 -05:00
wxiaoguang
2cc76009dc Fix navbar dropdown item align (#33782)
1.23 only

Fix #33781
2025-03-04 16:52:54 +08:00
Lunny Xiao
730742230f upgrade go-crypto from 1.1.4 to 1.1.6 (#33745) (#33754) 2025-03-01 22:23:55 +08:00
Giteabot
8939c3845a Disable go license generation as part of make tidy (#33747) (#33751)
Backport #33747 by @silverwind

It seems something broken `google/go-licenses` (maybe related to go
1.24), and my findings are in
https://github.com/google/go-licenses/issues/128#issuecomment-2689753365.
I think it's best we disable this generation for now until a better
solution is found.

Also, enable showing stderr output so we can actually debug this thing.
For reference, these are the errors that currently apparently break the
tool:

```
E0228 05:15:27.005759   13158 library.go:117] Package text/tabwriter does not have module info. Non go modules projects are no longer supported. For feedback, refer to https://github.com/google/go-licenses/issues/128.
E0228 05:15:27.005776   13158 library.go:117] Package net/http/fcgi does not have module info. Non go modules projects are no longer supported. For feedback, refer to https://github.com/google/go-licenses/issues/128.
F0228 05:15:27.028122   13158 main.go:77] some errors occurred when loading direct and transitive dependency packages
```

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
2025-02-28 17:13:19 -08:00
techknowlogick
d634e7576f bump x/oauth2 & x/crypto (#33704) (#33727)
Backport dep bump

---------

Co-authored-by: silverwind <me@silverwind.io>
2025-02-28 12:46:54 -05:00
Giteabot
7ded86f5af Remove superflous tw-content-center (#33741) (#33743)
Backport #33741 by @silverwind

Co-authored-by: silverwind <me@silverwind.io>
2025-02-28 12:38:27 -05:00
Giteabot
27a60fd91b Fix inconsistent closed issue list icon (#33722) (#33728)
Backport #33722 by @lunny

Fixe #33718 

Before 


![image](https://github.com/user-attachments/assets/2c77e249-a118-4471-8c63-ead4fe0f6336)


After 


![image](https://github.com/user-attachments/assets/c082eba8-5b21-4814-b091-c725ca46ccf3)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-02-25 17:29:07 -08:00
Jimmy Praet
e3021fae79 Use MatchPhraseQuery for bleve code search (#33628)
Fix regression from #32210 which unintentionally changed the search mode
for bleve from MaatchPhraseQuery to MatchQuery.

On the main branch, meanwhile with #33590 a "literal code search" mode
(by using quotes) was implemented as workaround for this unexpected code
search behavior. Maybe that feature needs some redesign as it turns out
to have been caused by a regression.

But this PR at least already fixes the regression for 1.23.x
2025-02-25 20:20:54 +00:00
Giteabot
81126daf53 Optimize user dashboard loading (#33686) (#33708)
Backport #33686 by @lunny

Fix #33582
Fix #31698

When a user login, the dashboard should load all feed belongs to him
with no any conditions. The complicated conditions should be applied
only for another user view this user's profile.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-02-24 20:34:03 -08:00
Giteabot
1c7339e385 Fix OCI image.version annotation for releases to use full semver (#33698) (#33701) 2025-02-24 09:13:45 -05:00
Giteabot
039924aa2a Try to fix ACME path when renew (#33668) (#33693)
Backport #33668 by wxiaoguang

Try to fix #32191

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-23 12:58:10 +00:00
Giteabot
b5007c6154 Fix for Maven Package Naming Convention Handling (#33678) (#33679)
Backport #33678 by dianaStr7

Co-authored-by: Diana <80010947+dianaStr7@users.noreply.github.com>
Co-authored-by: diana.strebkova@t-systems.com <diana.strebkova@t-systems.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-21 19:37:42 +00:00
Giteabot
ae595aa913 Improve Open-with URL encoding (#33666) (#33680)
Backport #33666 by wxiaoguang

Fix #33665

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-22 01:57:17 +08:00
Giteabot
aeeccc9642 Deleting repository should unlink all related packages (#33653) (#33673)
Backport #33653 by @lunny

Fix #33634

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-21 08:38:15 +00:00
Giteabot
37e99d9b34 Fix omitempty bug (#33663) (#33670)
Backport #33663 by @lunny

Fix #33660

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-02-21 00:08:05 -08:00
Giteabot
9da6d4ea85 Fix mCaptcha bug (#33659) (#33661)
Backport #33659 by wxiaoguang

Fix #33658

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-21 00:21:31 +08:00
Giteabot
8844e62cb4 git graph: don't show detached commits (#33645) (#33650)
Backport #33645 by ericLemanissier

Co-authored-by: ericLemanissier <ericLemanissier@users.noreply.github.com>
2025-02-20 09:11:43 +08:00
175 changed files with 2338 additions and 725 deletions

View File

@@ -202,12 +202,11 @@ jobs:
test-mssql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
# specifying the version of ubuntu in use as mssql fails on newer kernels
# pending resolution from vendor
runs-on: ubuntu-20.04
# NOTE: mssql-2017 docker image will panic when run on hosts that have Ubuntu newer than 20.04
runs-on: ubuntu-latest
services:
mssql:
image: mcr.microsoft.com/mssql/server:2017-latest
image: mcr.microsoft.com/mssql/server:2019-latest
env:
ACCEPT_EULA: Y
MSSQL_PID: Standard

View File

@@ -59,6 +59,8 @@ jobs:
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-docker-rootful:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -85,17 +87,27 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
tags: |-
gitea/gitea:${{ steps.clean_name.outputs.branch }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
nightly-docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -122,6 +134,12 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootless docker image
@@ -131,4 +149,6 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
file: Dockerfile.rootless
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
tags: |-
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless

View File

@@ -69,6 +69,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -79,7 +81,9 @@ jobs:
- uses: docker/metadata-action@v5
id: meta
with:
images: gitea/gitea
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
flavor: |
latest=false
# 1.2.3-rc0
@@ -90,16 +94,24 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -110,7 +122,9 @@ jobs:
- uses: docker/metadata-action@v5
id: meta
with:
images: gitea/gitea
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
latest=false
@@ -123,11 +137,17 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -14,6 +14,8 @@ concurrency:
jobs:
binary:
runs-on: namespace-profile-gitea-release-binary
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -71,6 +73,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
@@ -81,26 +85,34 @@ jobs:
- uses: docker/metadata-action@v5
id: meta
with:
images: gitea/gitea
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# this will generate tags in the following format:
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
@@ -116,7 +128,9 @@ jobs:
- uses: docker/metadata-action@v5
id: meta
with:
images: gitea/gitea
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless,onlatest=true
@@ -126,19 +140,25 @@ jobs:
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -4,6 +4,102 @@ 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.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
* SECURITY
* Fix a bug when uploading file via lfs ssh command (#34408) (#34411)
* Update net package (#34228) (#34232)
* BUGFIXES
* Fix releases sidebar navigation link (#34436) #34439
* Fix bug webhook milestone is not right. (#34419) #34429
* Fix two missed null value checks on the wiki page. (#34205) (#34215)
* Swift files can be passed either as file or as form value (#34068) (#34236)
* Fix bug when API get pull changed files for deleted head repository (#34333) (#34368)
* Upgrade github v61 -> v71 to fix migrating bug (#34389)
* Fix bug when visiting comparation page (#34334) (#34364)
* Fix wrong review requests when updating the pull request (#34286) (#34304)
* Fix github migration error when using multiple tokens (#34144) (#34302)
* Explicitly not update indexes when sync database schemas (#34281) (#34295)
* Fix panic when comment is nil (#34257) (#34277)
* Fix project board links to related Pull Requests (#34213) (#34222)
* Don't assume the default wiki branch is master in the wiki API (#34244) (#34245)
* DOCUMENTATION
* Update token creation API swagger documentation (#34288) (#34296)
* MISC
* Fix CI Build (#34315)
* Add riscv64 support (#34199) (#34204)
* 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
* Enhancements
* Add a config option to block "expensive" pages for anonymous users (#34024) (#34071)
* Also check default ssh-cert location for host (#34099) (#34100) (#34116)
* BUGFIXES
* Fix discord webhook 400 status code when description limit is exceeded (#34084) (#34124)
* Get changed files based on merge base when checking `pull_request` actions trigger (#34106) (#34120)
* Fix invalid version in RPM package path (#34112) (#34115)
* Return default avatar url when user id is zero rather than updating database (#34094) (#34095)
* Add additional ReplaceAll in pathsep to cater for different pathsep (#34061) (#34070)
* Try to fix check-attr bug (#34029) (#34033)
* Git client will follow 301 but 307 (#34005) (#34010)
* Fix block expensive for 1.23 (#34127)
* Fix markdown frontmatter rendering (#34102) (#34107)
* Add new CLI flags to set name and scopes when creating a user with access token (#34080) (#34103)
* Do not show 500 error when default branch doesn't exist (#34096) (#34097)
* Hide activity contributors, recent commits and code frequrency left tabs if there is no code permission (#34053) (#34065)
* Simplify emoji rendering (#34048) (#34049)
* Adjust the layout of the toolbar on the Issues/Projects page (#33667) (#34047)
* Pull request updates will also trigger code owners review requests (#33744) (#34045)
* Fix org repo creation being limited by user limits (#34030) (#34044)
* Fix git client accessing renamed repo (#34034) (#34043)
* Fix the issue with error message logging for the `check-attr` command on Windows OS. (#34035) (#34036)
* Polyfill WeakRef (#34025) (#34028)
## [1.23.6](https://github.com/go-gitea/gitea/releases/tag/v1.23.6) - 2025-03-24
* SECURITY
* Fix LFS URL (#33840) (#33843)
* Update jwt and redis packages (#33984) (#33987)
* Update golang crypto and net (#33989)
* BUGFIXES
* Drop timeout for requests made to the internal hook api (#33947) (#33970)
* Fix maven panic when no package exists (#33888) (#33889)
* Fix markdown render (#33870) (#33875)
* Fix auto concurrency cancellation skips commit status updates (#33764) (#33849)
* Fix oauth2 auth (#33961) (#33962)
* Fix incorrect 1.23 translations (#33932)
* Try to figure out attribute checker problem (#33901) (#33902)
* Ignore trivial errors when updating push data (#33864) (#33887)
* Fix some UI problems for 1.23 (#33856)
* Removing unwanted ui container (#33833) (#33835)
* Support disable passkey auth (#33348) (#33819)
* Do not call "git diff" when listing PRs (#33817)
* Try to fix ACME (3rd) (#33807) (#33808)
* Fix incorrect code search indexer options (#33992) #33999
## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-03
* SECURITY
* Bump x/oauth2 & x/crypto (#33704) (#33727)
* PERFORMANCE
* Optimize user dashboard loading (#33686) (#33708)
* BUGFIXES
* Fix navbar dropdown item align (#33782)
* Fix inconsistent closed issue list icon (#33722) (#33728)
* Fix for Maven Package Naming Convention Handling (#33678) (#33679)
* Improve Open-with URL encoding (#33666) (#33680)
* Deleting repository should unlink all related packages (#33653) (#33673)
* Fix omitempty bug (#33663) (#33670)
* Upgrade go-crypto from 1.1.4 to 1.1.6 (#33745) (#33754)
* Fix OCI image.version annotation for releases to use full semver (#33698) (#33701)
* Try to fix ACME path when renew (#33668) (#33693)
* Fix mCaptcha bug (#33659) (#33661)
* Git graph: don't show detached commits (#33645) (#33650)
* Use MatchPhraseQuery for bleve code search (#33628)
* Adjust appearence of commit status webhook (#33778) #33789
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
* SECURITY

View File

@@ -109,7 +109,7 @@ endif
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64
GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/))
MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
@@ -508,7 +508,7 @@ unit-test-coverage:
tidy:
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2))
$(GO) mod tidy -compat=$(MIN_GO_VERSION)
@$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
$(MAKE) --no-print-directory $(GO_LICENSE_FILE)
vendor: go.mod go.sum
$(GO) mod vendor

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
Name: "access-token",
Usage: "Generate access token for the user",
},
&cli.StringFlag{
Name: "access-token-name",
Usage: `Name of the generated access token`,
Value: "gitea-admin",
},
&cli.StringFlag{
Name: "access-token-scopes",
Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
Value: "all",
},
&cli.BoolFlag{
Name: "restricted",
Usage: "Make a restricted user account",
@@ -162,23 +173,39 @@ func runCreateUser(c *cli.Context) error {
IsRestricted: restricted,
}
var accessTokenName string
var accessTokenScope auth_model.AccessTokenScope
if c.IsSet("access-token") {
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
if accessTokenName == "" {
return errors.New("access-token-name cannot be empty")
}
var err error
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
}
// arguments should be prepared before creating the user & access token, in case there is anything wrong
// create the user
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err)
}
if c.Bool("access-token") {
t := &auth_model.AccessToken{
Name: "gitea-admin",
UID: u.ID,
}
// create the access token
if accessTokenScope != "" {
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
if err := auth_model.NewAccessToken(ctx, t); err != nil {
return err
}
fmt.Printf("Access token was successfully created... %s\n", t.Token)
}
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}

View File

@@ -8,37 +8,97 @@ import (
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAdminUserCreate(t *testing.T) {
app := NewMainApp(AppVersion{})
reset := func() {
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
}
type createCheck struct{ IsAdmin, MustChangePassword bool }
createUser := func(name, args string) createCheck {
t.Run("MustChangePassword", func(t *testing.T) {
type check struct{ IsAdmin, MustChangePassword bool }
createCheck := func(name, args string) check {
assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
return createCheck{u.IsAdmin, u.MustChangePassword}
return check{u.IsAdmin, u.MustChangePassword}
}
reset()
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
reset()
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
reset()
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
})
createUser := func(name, args string) error {
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
}
t.Run("AccessToken", func(t *testing.T) {
// no generated access token
reset()
assert.NoError(t, createUser("u", "--random-password"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
// using "--access-token" only means "all" access
reset()
assert.NoError(t, createUser("u", "--random-password --access-token"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
assert.NoError(t, err)
assert.True(t, hasScopes)
// using "--access-token" with name & scopes
reset()
assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
assert.NoError(t, err)
assert.True(t, hasScopes)
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
assert.NoError(t, err)
assert.False(t, hasScopes)
// using "--access-token-name" without "--access-token"
reset()
err = createUser("u", "--random-password --access-token-name new-token-name")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
// using "--access-token-scopes" without "--access-token"
reset()
err = createUser("u", "--random-password --access-token-scopes read:issue")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
// empty permission
reset()
err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access token does not have any permission")
})
}

View File

@@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
},
&cli.StringFlag{
Name: "scopes",
Value: "",
Usage: "Comma separated list of scopes to apply to access token",
Value: "all",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
},
},
Action: runGenerateAccessToken,
@@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") {
return errors.New("You must provide a username to generate a token for")
return errors.New("you must provide a username to generate a token for")
}
ctx, cancel := installSignals()
@@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
t.Scope = accessTokenScope
// create the token

View File

@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/caddyserver/certmagic"
)
@@ -54,10 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p
}
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
magic := certmagic.NewDefault()
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool
if setting.AcmeCARoot != "" {
@@ -67,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
}
}
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
CA: setting.AcmeURL,
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
// And one more thing, no idea why we should set the global default variables here
// But it seems that the current ACME code needs these global variables to make renew work.
// Otherwise, "renew" will use incorrect storage path
oldDefaultACME := certmagic.DefaultACME
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
certmagic.DefaultACME = certmagic.ACMEIssuer{
// try to use the default values provided by DefaultACME
CA: util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
TestCA: oldDefaultACME.TestCA,
Logger: oldDefaultACME.Logger,
HTTPProxy: oldDefaultACME.HTTPProxy,
TrustedRoots: certPool,
Email: setting.AcmeEmail,
Agreed: setting.AcmeTOS,
@@ -77,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort,
})
}
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary

View File

@@ -17,7 +17,7 @@ import (
"strings"
"syscall"
"github.com/google/go-github/v61/github"
"github.com/google/go-github/v71/github"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
)

View File

@@ -774,6 +774,9 @@ LEVEL = Info
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
;;
;; User must sign in to view anything.
;; After 1.23.7, it could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources,
;; for example: block anonymous AI crawlers from accessing repo code pages.
;; The "expensive" mode is experimental and subject to change.
;REQUIRE_SIGNIN_VIEW = false
;;
;; Mail notification
@@ -784,10 +787,13 @@ LEVEL = Info
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
;ENABLE_BASIC_AUTHENTICATION = true
;;
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
;ENABLE_PASSWORD_SIGNIN_FORM = true
;;
;; Allow users to sign-in with a passkey
;ENABLE_PASSKEY_AUTHENTICATION = true
;;
;; More detail: https://github.com/gogits/gogs/issues/165
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.

View File

@@ -22,3 +22,8 @@ manifests:
architecture: arm64
os: linux
variant: v8
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64-rootless
platform:
architecture: riscv64
os: linux

View File

@@ -22,3 +22,8 @@ manifests:
architecture: arm64
os: linux
variant: v8
-
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}nightly{{/if}}-linux-riscv64
platform:
architecture: riscv64
os: linux

View File

@@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"}
fi
# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to
# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section),
# the generated key file name is `{keyname}-cert.pub`
if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then
SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"}
fi
if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then
SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"}
fi
if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then
SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"}
fi
if [ -d /etc/ssh ]; then
SSH_PORT=${SSH_PORT:-"22"} \
SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \

26
go.mod
View File

@@ -1,6 +1,6 @@
module code.gitea.io/gitea
go 1.23
go 1.23.8
// 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:
@@ -24,7 +24,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.1.4
github.com/ProtonMail/go-crypto v1.1.6
github.com/PuerkitoBio/goquery v1.10.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/alecthomas/chroma/v2 v2.15.0
@@ -64,8 +64,8 @@ require (
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/go-github/v61 v61.0.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-github/v71 v71.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
github.com/google/uuid v1.6.0
@@ -100,7 +100,7 @@ require (
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.20.5
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.7.0
github.com/redis/go-redis/v9 v9.7.3
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0
@@ -118,13 +118,13 @@ require (
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.36.0
golang.org/x/image v0.21.0
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.10.0
golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0
golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.27.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
golang.org/x/tools v0.29.0
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
@@ -215,7 +215,7 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-webauthn/x v0.1.15 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
@@ -325,6 +325,8 @@ replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-tra
// TODO: This could be removed after https://github.com/mholt/archiver/pull/396 merged
replace github.com/mholt/archiver/v3 => github.com/anchore/archiver/v3 v3.5.2
replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078
exclude github.com/gofrs/uuid v3.2.0+incompatible
exclude github.com/gofrs/uuid v4.0.0+incompatible

54
go.sum
View File

@@ -14,12 +14,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
gitea.com/gitea/act v0.261.3 h1:BhiYpGJQKGq0XMYYICCYAN4KnsEWHyLbA6dxhZwFcV4=
gitea.com/gitea/act v0.261.3/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/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=
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw=
gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g=
@@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
@@ -373,10 +373,11 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -409,10 +410,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30=
github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
@@ -658,8 +660,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=
github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
@@ -833,8 +835,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
@@ -869,10 +871,10 @@ 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.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -884,8 +886,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.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -919,8 +921,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.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.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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=
@@ -932,8 +934,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -944,8 +946,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.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -194,7 +194,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
// CancelPreviousJobs cancels all previous jobs of the same repository, reference, workflow, and event.
// It's useful when a new run is triggered, and all previous runs needn't be continued anymore.
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) ([]*ActionRunJob, error) {
// Find all runs in the specified repository, reference, and workflow with non-final status
runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{
RepoID: repoID,
@@ -204,14 +204,16 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
Status: []Status{StatusRunning, StatusWaiting, StatusBlocked},
})
if err != nil {
return err
return nil, err
}
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
if total == 0 {
return nil
return nil, nil
}
cancelledJobs := make([]*ActionRunJob, 0, total)
// Iterate over each found run and cancel its associated jobs.
for _, run := range runs {
// Find all jobs associated with the current run.
@@ -219,7 +221,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
RunID: run.ID,
})
if err != nil {
return err
return cancelledJobs, err
}
// Iterate over each job and attempt to cancel it.
@@ -238,27 +240,29 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
// Update the job's status and stopped time in the database.
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
if err != nil {
return err
return cancelledJobs, err
}
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
if n == 0 {
return fmt.Errorf("job has changed, try again")
return cancelledJobs, fmt.Errorf("job has changed, try again")
}
cancelledJobs = append(cancelledJobs, job)
// Continue with the next job.
continue
}
// If the job has an associated task, try to stop the task, effectively cancelling the job.
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
return err
return cancelledJobs, err
}
cancelledJobs = append(cancelledJobs, job)
}
}
// Return nil to indicate successful cancellation of all running and waiting jobs.
return nil
return cancelledJobs, nil
}
// InsertRun inserts a run

View File

@@ -120,21 +120,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error {
return committer.Commit()
}
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) ([]*ActionRunJob, error) {
// If actions disabled when there is schedule task, this will remove the outdated schedule tasks
// There is no other place we can do this because the app.ini will be changed manually
if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
return fmt.Errorf("DeleteCronTaskByRepo: %v", err)
return nil, fmt.Errorf("DeleteCronTaskByRepo: %v", err)
}
// cancel running cron jobs of this repository and delete old schedules
if err := CancelPreviousJobs(
jobs, err := CancelPreviousJobs(
ctx,
repo.ID,
repo.DefaultBranch,
"",
webhook_module.HookEventSchedule,
); err != nil {
return fmt.Errorf("CancelPreviousJobs: %v", err)
)
if err != nil {
return jobs, fmt.Errorf("CancelPreviousJobs: %v", err)
}
return nil
return jobs, nil
}

View File

@@ -454,6 +454,24 @@ func ActivityReadable(user, doer *user_model.User) bool {
doer != nil && (doer.IsAdmin || user.ID == doer.ID)
}
func FeedDateCond(opts GetFeedsOptions) builder.Cond {
cond := builder.NewCond()
if opts.Date == "" {
return cond
}
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
if err != nil {
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
} else {
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
}
return cond
}
func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) {
cond := builder.NewCond()
@@ -534,17 +552,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
cond = cond.And(builder.Eq{"is_deleted": false})
}
if opts.Date != "" {
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
if err != nil {
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
} else {
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
cond = cond.And(builder.Gte{"`action`.created_unix": dateLow.Unix()})
cond = cond.And(builder.Lte{"`action`.created_unix": dateHigh.Unix()})
}
}
cond = cond.And(FeedDateCond(opts))
return cond, nil
}

View File

@@ -208,10 +208,32 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo")
}
cond, err := ActivityQueryCondition(ctx, opts)
var err error
var cond builder.Cond
// if the actor is the requested user or is an administrator, we can skip the ActivityQueryCondition
if opts.Actor != nil && opts.RequestedUser != nil && (opts.Actor.IsAdmin || opts.Actor.ID == opts.RequestedUser.ID) {
cond = builder.Eq{
"user_id": opts.RequestedUser.ID,
}.And(
FeedDateCond(opts),
)
if !opts.IncludeDeleted {
cond = cond.And(builder.Eq{"is_deleted": false})
}
if !opts.IncludePrivate {
cond = cond.And(builder.Eq{"is_private": false})
}
if opts.OnlyPerformedBy {
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
}
} else {
cond, err = ActivityQueryCondition(ctx, opts)
if err != nil {
return nil, 0, err
}
}
actions := make([]*Action, 0, opts.PageSize)
var count int64

View File

@@ -44,7 +44,7 @@ func init() {
// TranslatableMessage represents JSON struct that can be translated with a Locale
type TranslatableMessage struct {
Format string
Args []any `json:"omitempty"`
Args []any `json:",omitempty"`
}
// LoadRepo loads repository of the task

View File

@@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
return bitmap.toScope(), nil
}
func (s AccessTokenScope) HasPermissionScope() bool {
return s != "" && s != AccessTokenScopePublicOnly
}
// PublicOnly checks if this token scope is limited to public resources
func (s AccessTokenScope) PublicOnly() (bool, error) {
bitmap, err := s.parse()

View File

@@ -173,6 +173,18 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
return &branch, nil
}
// IsBranchExist returns true if the branch exists in the repository.
func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) {
var branch Branch
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
if err != nil {
return false, err
} else if !has {
return false, nil
}
return !branch.IsDeleted, nil
}
func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) {
branches := make([]*Branch, 0, len(branchNames))

View File

@@ -663,7 +663,7 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
}
if review != nil {
// skip it when reviewer hase been request to review
// skip it when reviewer has been request to review
if review.Type == ReviewTypeRequest {
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
}

View File

@@ -14,5 +14,9 @@ func AddContentVersionToIssueAndComment(x *xorm.Engine) error {
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
}
return x.Sync(new(Comment), new(Issue))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(Comment), new(Issue))
return err
}

View File

@@ -13,5 +13,9 @@ func AddForcePushBranchProtection(x *xorm.Engine) error {
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(new(ProtectedBranch))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(ProtectedBranch))
return err
}

View File

@@ -10,5 +10,9 @@ func AddSkipSecondaryAuthColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
type oauth2Application struct {
SkipSecondaryAuthorization bool `xorm:"NOT NULL DEFAULT FALSE"`
}
return x.Sync(new(oauth2Application))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(oauth2Application))
return err
}

View File

@@ -19,5 +19,9 @@ func AddCommentMetaDataColumn(x *xorm.Engine) error {
CommentMetaData *CommentMetaData `xorm:"JSON TEXT"` // put all non-index metadata in a single field
}
return x.Sync(new(Comment))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(Comment))
return err
}

View File

@@ -9,5 +9,9 @@ func AddBlockAdminMergeOverrideBranchProtection(x *xorm.Engine) error {
type ProtectedBranch struct {
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
}
return x.Sync(new(ProtectedBranch))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(ProtectedBranch))
return err
}

View File

@@ -12,5 +12,9 @@ func AddPriorityToProtectedBranch(x *xorm.Engine) error {
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
}
return x.Sync(new(ProtectedBranch))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(ProtectedBranch))
return err
}

View File

@@ -11,6 +11,9 @@ func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
type Issue struct {
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
}
return x.Sync(new(Issue))
_, err := x.SyncWithOptions(xorm.SyncOptions{
IgnoreConstrains: true,
IgnoreIndices: true,
}, new(Issue))
return err
}

View File

@@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
default:
e.Desc("package_version.created_unix")
}
// Sort by id for stable order with duplicates in the other field
e.Asc("package_version.id")
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
}
// SearchVersions gets all versions of packages matching the search options

View File

@@ -61,7 +61,9 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
if u.IsGhost() || u.IsGiteaActions() {
// ghost user was deleted, Gitea actions is a bot user, 0 means the user should be a virtual user
// which comes from git configure information
if u.IsGhost() || u.IsGiteaActions() || u.ID <= 0 {
return avatars.DefaultAvatarLink()
}

View File

@@ -463,7 +463,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
matchTimes++
}
case "paths":
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
} else {
@@ -476,7 +476,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
}
}
case "paths-ignore":
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref)
filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase)
if err != nil {
log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err)
} else {

View File

@@ -46,6 +46,7 @@ type Command struct {
desc string
globalArgsLength int
brokenArgs []string
cmd *exec.Cmd // for debug purpose only
}
func (c *Command) String() string {
@@ -314,6 +315,7 @@ func (c *Command) Run(opts *RunOpts) error {
startTime := time.Now()
cmd := exec.CommandContext(ctx, c.prog, c.args...)
c.cmd = cmd // for debug purpose only
if opts.Env == nil {
cmd.Env = os.Environ()
} else {
@@ -348,9 +350,10 @@ func (c *Command) Run(opts *RunOpts) error {
// We need to check if the context is canceled by the program on Windows.
// This is because Windows does not have signal checking when terminating the process.
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
if runtime.GOOS == "windows" &&
err != nil &&
err.Error() == "" &&
(err.Error() == "" || err.Error() == "exit status 1") &&
cmd.ProcessState.ExitCode() == 1 &&
ctx.Err() == context.Canceled {
return ctx.Err()

View File

@@ -9,6 +9,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"code.gitea.io/gitea/modules/log"
)
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
stdinReader io.ReadCloser
stdinWriter *os.File
stdOut attributeWriter
stdOut *nulSeparatedAttributeWriter
cmd *Command
env []string
ctx context.Context
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
return nil
}
// Run run cmd
func (c *CheckAttributeReader) Run() error {
defer func() {
_ = c.stdinReader.Close()
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
defer func() {
if err != nil && err != c.ctx.Err() {
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
}
}()
@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
return nil, err
}
reportTimeout := func() error {
stdOutClosed := false
select {
case <-c.stdOut.closed:
stdOutClosed = true
default:
}
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
if c.cmd.cmd != nil {
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
}
_ = c.Close()
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
}
rs = make(map[string]string)
for range c.Attributes {
select {
case <-time.After(5 * time.Second):
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
// So add a timeout here to mitigate the problem, and output more logs for debug purpose
// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
// and at the moment the CheckPath result is not so important, so we can just ignore it.
return nil, reportTimeout()
case attr, ok := <-c.stdOut.ReadAttribute():
if !ok {
return nil, c.ctx.Err()
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
return rs, nil
}
// Close close pip after use
func (c *CheckAttributeReader) Close() error {
c.cancel()
err := c.stdinWriter.Close()
return err
}
type attributeWriter interface {
io.WriteCloser
ReadAttribute() <-chan attributeTriple
}
type attributeTriple struct {
Filename string
Attribute string
@@ -263,7 +280,7 @@ func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
}
}
wr.tmp = append(wr.tmp, p...)
return len(p), nil
return l, nil
}
func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
return nil
}
// Create a check attribute reader for the current repository and provided commit ID
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
if err != nil {
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
}
ctx, cancel := context.WithCancel(repo.Ctx)
if err := checker.Init(ctx); err != nil {
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
} else {
go func() {
err := checker.Run()
if err != nil && err != ctx.Err() {
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
if err != nil && !IsErrCanceledOrKilled(err) {
log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
}
cancel()
}()
}
deferable := func() {
deferrable := func() {
_ = checker.Close()
cancel()
deleteTemporaryFile()
}
return checker, deferable
return checker, deferrable
}

View File

@@ -4,10 +4,16 @@
package git
import (
"context"
mathRand "math/rand/v2"
"path/filepath"
"slices"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
Value: "unspecified",
}, attr)
}
func TestAttributeReader(t *testing.T) {
t.Skip() // for debug purpose only, do not run in CI
ctx := context.Background()
timeout := 1 * time.Second
repoPath := filepath.Join(testReposDir, "language_stats_repo")
commitRef := "HEAD"
oneRound := func(t *testing.T, roundIdx int) {
ctx, cancel := context.WithTimeout(ctx, timeout)
_ = cancel
gitRepo, err := OpenRepository(ctx, repoPath)
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(commitRef)
require.NoError(t, err)
files, err := gitRepo.LsFiles()
require.NoError(t, err)
randomFiles := slices.Clone(files)
randomFiles = append(randomFiles, "any-file-1", "any-file-2")
t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
defer deferrable()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
file := randomFiles[mathRand.IntN(len(randomFiles))]
_, err := attrReader.CheckPath(file)
if err != nil {
for i := 0; i < 10; i++ {
_, _ = attrReader.CheckPath(file)
}
break
}
}
wg.Done()
}()
wg.Wait()
}
for i := 0; i < 100; i++ {
oneRound(t, i)
}
}

View File

@@ -29,7 +29,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
}
if len(branches) == 0 {
graphCmd.AddArguments("--all")
graphCmd.AddArguments("--tags", "--branches")
}
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").

View File

@@ -8,6 +8,7 @@ import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
@@ -101,6 +102,9 @@ func (r *Request) Param(key, value string) *Request {
// Body adds request raw body. It supports string, []byte and io.Reader as body.
func (r *Request) Body(data any) *Request {
if r == nil {
return nil
}
switch t := data.(type) {
case nil: // do nothing
case string:
@@ -193,6 +197,9 @@ func (r *Request) getResponse() (*http.Response, error) {
// 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()
}

View File

@@ -28,7 +28,6 @@ import (
"github.com/blevesearch/bleve/v2"
analyzer_custom "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
analyzer_keyword "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
"github.com/blevesearch/bleve/v2/analysis/tokenizer/letter"
@@ -70,7 +69,7 @@ const (
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
filenameIndexerTokenizer = "filenameIndexerTokenizer"
repoIndexerDocType = "repoIndexerDocType"
repoIndexerLatestVersion = 8
repoIndexerLatestVersion = 9
)
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
@@ -107,7 +106,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
"type": analyzer_custom.Name,
"char_filters": []string{},
"tokenizer": letter.Name,
"token_filters": []string{unicodeNormalizeName, camelcase.Name, lowercase.Name},
"token_filters": []string{unicodeNormalizeName, lowercase.Name},
}); err != nil {
return nil, err
}
@@ -266,7 +265,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
pathQuery.FieldVal = "Filename"
pathQuery.SetBoost(10)
contentQuery := bleve.NewMatchQuery(opts.Keyword)
contentQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
contentQuery.FieldVal = "Content"
if opts.IsKeywordFuzzy {

View File

@@ -165,35 +165,6 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
},
},
},
// Search for matches on the contents of files within the repo '62'.
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
{
RepoIDs: []int64{62},
Keyword: "This is not cheese",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "potato/ham.md",
Content: "This is not cheese",
},
{
Filename: "ham.md",
Content: "This is also not cheese",
},
},
},
// Search for matches on the contents of files regardless of case.
{
RepoIDs: nil,
Keyword: "dESCRIPTION",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "README.md",
Content: "# repo1\n\nDescription for repo1",
},
},
},
// Search for an exact match on the filename within the repo '62' (case insenstive).
// This scenario yields a single result (the file avocado.md on the repo '62')
{
@@ -233,6 +204,47 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
},
}
if name == "elastic_search" {
// Additional scenarios for elastic_search only
additional := []struct {
RepoIDs []int64
Keyword string
Langs int
Results []codeSearchResult
}{
// Search for matches on the contents of files within the repo '62'.
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
{
RepoIDs: []int64{62},
Keyword: "This is not cheese",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "potato/ham.md",
Content: "This is not cheese",
},
{
Filename: "ham.md",
Content: "This is also not cheese",
},
},
},
// Search for matches on the contents of files regardless of case.
{
RepoIDs: nil,
Keyword: "dESCRIPTION",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "README.md",
Content: "# repo1\n\nDescription for repo1",
},
},
},
}
keywords = append(keywords, additional...)
}
for _, kw := range keywords {
t.Run(kw.Keyword, func(t *testing.T) {
total, res, langs, err := indexer.Search(context.TODO(), &internal.SearchOptions{

View File

@@ -70,14 +70,13 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
g.logger.Log("json marshal error", err)
return nil, err
}
url := g.server.JoinPath("objects/batch").String()
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeGitLFS,
headerContentType: mimeGitLFS,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
resp, err := req.Response()
if err != nil {
g.logger.Log("http request error", err)
@@ -179,13 +178,12 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
g.logger.Log("argument id incorrect")
return nil, 0, transfer.ErrCorruptData
}
url := action.Href
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeOctetStream,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
resp, err := req.Response()
if err != nil {
return nil, 0, fmt.Errorf("failed to get response: %w", err)
@@ -225,7 +223,6 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
g.logger.Log("argument id incorrect")
return transfer.ErrCorruptData
}
url := action.Href
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
@@ -233,7 +230,7 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
headerContentLength: strconv.FormatInt(size, 10),
}
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
req.Body(r)
resp, err := req.Response()
if err != nil {
@@ -274,14 +271,13 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
// the server sent no verify action
return transfer.SuccessStatus(), nil
}
url := action.Href
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeGitLFS,
headerContentType: mimeGitLFS,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
resp, err := req.Response()
if err != nil {
return transfer.NewStatus(transfer.StatusInternalServerError), err

View File

@@ -43,14 +43,13 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
g.logger.Log("json marshal error", err)
return nil, err
}
url := g.server.String()
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeGitLFS,
headerContentType: mimeGitLFS,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
resp, err := req.Response()
if err != nil {
g.logger.Log("http request error", err)
@@ -95,14 +94,13 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
g.logger.Log("json marshal error", err)
return err
}
url := g.server.JoinPath(lock.ID(), "unlock").String()
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeGitLFS,
headerContentType: mimeGitLFS,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
resp, err := req.Response()
if err != nil {
g.logger.Log("http request error", err)
@@ -176,16 +174,15 @@ func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lo
}
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
urlq := g.server.JoinPath() // get a copy
urlq.RawQuery = v.Encode()
url := urlq.String()
serverURLWithQuery := g.server.JoinPath() // get a copy
serverURLWithQuery.RawQuery = v.Encode()
headers := map[string]string{
headerAuthorization: g.authToken,
headerGiteaInternalAuth: g.internalAuth,
headerAccept: mimeGitLFS,
headerContentType: mimeGitLFS,
}
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
resp, err := req.Response()
if err != nil {
g.logger.Log("http request error", err)

View File

@@ -8,9 +8,13 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/charmbracelet/git-lfs-transfer/transfer"
)
@@ -57,8 +61,7 @@ const (
// Operations enum
const (
opNone = iota
opDownload
opDownload = iota + 1
opUpload
)
@@ -86,8 +89,49 @@ func statusCodeToErr(code int) error {
}
}
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
req := private.NewInternalRequest(ctx, url, method)
func toInternalLFSURL(s string) string {
pos1 := strings.Index(s, "://")
if pos1 == -1 {
return ""
}
appSubURLWithSlash := setting.AppSubURL + "/"
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
if pos2 == -1 {
return ""
}
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
fields := strings.SplitN(routePath, "/", 3)
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
return ""
}
return setting.LocalURL + "api/internal/repo/" + routePath
}
func isInternalLFSURL(s string) bool {
if !strings.HasPrefix(s, setting.LocalURL) {
return false
}
u, err := url.Parse(s)
if err != nil {
return false
}
routePath := util.PathJoinRelX(u.Path)
subRoutePath, cut := strings.CutPrefix(routePath, "api/internal/repo/")
if !cut {
return false
}
fields := strings.SplitN(subRoutePath, "/", 3)
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
return false
}
return true
}
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
if !isInternalLFSURL(internalURL) {
return nil
}
req := private.NewInternalRequest(ctx, internalURL, method)
for k, v := range headers {
req.Header(k, v)
}

View File

@@ -0,0 +1,54 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package backend
import (
"context"
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
func TestToInternalLFSURL(t *testing.T) {
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
cases := []struct {
url string
expected string
}{
{"http://appurl/any", ""},
{"http://appurl/sub/any", ""},
{"http://appurl/sub/owner/repo/any", ""},
{"http://appurl/sub/owner/repo/info/any", ""},
{"http://appurl/sub/owner/repo/info/lfs/any", "http://localurl/api/internal/repo/owner/repo/info/lfs/any"},
}
for _, c := range cases {
assert.Equal(t, c.expected, toInternalLFSURL(c.url), c.url)
}
}
func TestIsInternalLFSURL(t *testing.T) {
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
defer test.MockVariableValue(&setting.InternalToken, "mock-token")()
cases := []struct {
url string
expected bool
}{
{"", false},
{"http://otherurl/api/internal/repo/owner/repo/info/lfs/any", false},
{"http://localurl/api/internal/repo/owner/repo/info/lfs/any", true},
{"http://localurl/api/internal/repo/owner/repo/info", false},
{"http://localurl/api/internal/misc/owner/repo/info/lfs/any", false},
{"http://localurl/api/internal/owner/repo/info/lfs/any", false},
{"http://localurl/api/internal/foo/bar", false},
}
for _, c := range cases {
req := newInternalRequestLFS(context.Background(), c.url, "GET", nil, nil)
assert.Equal(t, c.expected, req != nil, c.url)
assert.Equal(t, c.expected, isInternalLFSURL(c.url), c.url)
}
}

View File

@@ -4,6 +4,7 @@
package markdown
import (
"html/template"
"strconv"
"github.com/yuin/goldmark/ast"
@@ -29,9 +30,7 @@ func (n *Details) Kind() ast.NodeKind {
// NewDetails returns a new Paragraph node.
func NewDetails() *Details {
return &Details{
BaseBlock: ast.BaseBlock{},
}
return &Details{}
}
// Summary is a block that contains the summary of details block
@@ -54,9 +53,7 @@ func (n *Summary) Kind() ast.NodeKind {
// NewSummary returns a new Summary node.
func NewSummary() *Summary {
return &Summary{
BaseBlock: ast.BaseBlock{},
}
return &Summary{}
}
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
@@ -95,29 +92,6 @@ type Icon struct {
Name []byte
}
// Dump implements Node.Dump .
func (n *Icon) Dump(source []byte, level int) {
m := map[string]string{}
m["Name"] = string(n.Name)
ast.DumpHelper(n, source, level, m, nil)
}
// KindIcon is the NodeKind for Icon
var KindIcon = ast.NewNodeKind("Icon")
// Kind implements Node.Kind.
func (n *Icon) Kind() ast.NodeKind {
return KindIcon
}
// NewIcon returns a new Paragraph node.
func NewIcon(name string) *Icon {
return &Icon{
BaseInline: ast.BaseInline{},
Name: []byte(name),
}
}
// ColorPreview is an inline for a color preview
type ColorPreview struct {
ast.BaseInline
@@ -175,3 +149,24 @@ func NewAttention(attentionType string) *Attention {
AttentionType: attentionType,
}
}
var KindRawHTML = ast.NewNodeKind("RawHTML")
type RawHTML struct {
ast.BaseBlock
rawHTML template.HTML
}
func (n *RawHTML) Dump(source []byte, level int) {
m := map[string]string{}
m["RawHTML"] = string(n.rawHTML)
ast.DumpHelper(n, source, level, m, nil)
}
func (n *RawHTML) Kind() ast.NodeKind {
return KindRawHTML
}
func NewRawHTML(rawHTML template.HTML) *RawHTML {
return &RawHTML{rawHTML: rawHTML}
}

View File

@@ -4,25 +4,24 @@
package markdown
import (
"strings"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/svg"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"gopkg.in/yaml.v3"
)
func nodeToTable(meta *yaml.Node) ast.Node {
for {
for meta != nil && meta.Kind == yaml.DocumentNode {
meta = meta.Content[0]
}
if meta == nil {
return nil
}
switch meta.Kind {
case yaml.DocumentNode:
meta = meta.Content[0]
continue
default:
}
break
}
switch meta.Kind {
case yaml.MappingNode:
return mappingNodeToTable(meta)
case yaml.SequenceNode:
@@ -72,12 +71,28 @@ func sequenceNodeToTable(meta *yaml.Node) ast.Node {
return table
}
func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
func nodeToDetails(g *ASTTransformer, meta *yaml.Node) ast.Node {
for meta != nil && meta.Kind == yaml.DocumentNode {
meta = meta.Content[0]
}
if meta == nil {
return nil
}
if meta.Kind != yaml.MappingNode {
return nil
}
var keys []string
for i := 0; i < len(meta.Content); i += 2 {
if meta.Content[i].Kind == yaml.ScalarNode {
keys = append(keys, meta.Content[i].Value)
}
}
details := NewDetails()
details.SetAttributeString(g.renderInternal.SafeAttr("class"), g.renderInternal.SafeValue("frontmatter-content"))
summary := NewSummary()
summary.AppendChild(summary, NewIcon(icon))
summaryInnerHTML := htmlutil.HTMLFormat("%s %s", svg.RenderHTML("octicon-table", 12), strings.Join(keys, ", "))
summary.AppendChild(summary, NewRawHTML(summaryInnerHTML))
details.AppendChild(details, summary)
details.AppendChild(details, nodeToTable(meta))
return details
}

View File

@@ -5,9 +5,6 @@ package markdown
import (
"fmt"
"regexp"
"strings"
"sync"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/markup"
@@ -51,7 +48,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
tocList := make([]Header, 0, 20)
if rc.yamlNode != nil {
metaNode := rc.toMetaNode()
metaNode := rc.toMetaNode(g)
if metaNode != nil {
node.InsertBefore(node, firstChild, metaNode)
}
@@ -111,11 +108,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
}
// it is copied from old code, which is quite doubtful whether it is correct
var reValidIconName = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
})
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
r := &HTMLRenderer{
@@ -140,11 +132,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindDocument, r.renderDocument)
reg.Register(KindDetails, r.renderDetails)
reg.Register(KindSummary, r.renderSummary)
reg.Register(KindIcon, r.renderIcon)
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
reg.Register(KindAttention, r.renderAttention)
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
reg.Register(KindRawHTML, r.renderRawHTML)
}
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -206,30 +198,14 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, nil
}
func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
n := node.(*Icon)
name := strings.TrimSpace(strings.ToLower(string(n.Name)))
if len(name) == 0 {
// skip this
return ast.WalkContinue, nil
}
if !reValidIconName().MatchString(name) {
// skip this
return ast.WalkContinue, nil
}
// FIXME: the "icon xxx" is from Fomantic UI, it's really questionable whether it still works correctly
err := r.renderInternal.FormatWithSafeAttrs(w, `<i class="icon %s"></i>`, name)
n := node.(*RawHTML)
_, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
if err != nil {
return ast.WalkStop, err
}
return ast.WalkContinue, nil
}

View File

@@ -159,21 +159,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
limit: setting.UI.MaxDisplayFileSize * 3,
}
// FIXME: should we include a timeout to abort the renderer if it takes too long?
defer func() {
err := recover()
if err == nil {
return
}
log.Warn("Unable to render markdown due to panic in goldmark: %v", err)
if (!setting.IsProd && !setting.IsInTesting) || log.IsDebug() {
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
}
}()
// FIXME: Don't read all to memory, but goldmark doesn't support
pc := newParserContext(ctx)
buf, err := io.ReadAll(input)
if err != nil {
log.Error("Unable to ReadAll: %v", err)
@@ -181,14 +167,24 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
}
buf = giteautil.NormalizeEOL(buf)
// FIXME: should we include a timeout to abort the renderer if it takes too long?
defer func() {
err := recover()
if err == nil {
return
}
log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
escapedHTML := template.HTMLEscapeString(giteautil.UnsafeBytesToString(buf))
_, _ = output.Write(giteautil.UnsafeStringToBytes(escapedHTML))
}()
pc := newParserContext(ctx)
// Preserve original length.
bufWithMetadataLength := len(buf)
rc := &RenderConfig{
Meta: markup.RenderMetaAsDetails,
Icon: "table",
Lang: "",
}
rc := &RenderConfig{Meta: markup.RenderMetaAsDetails}
buf, _ = ExtractMetadataBytes(buf, rc)
metaLength := bufWithMetadataLength - len(buf)

View File

@@ -23,6 +23,11 @@ func TestAttention(t *testing.T) {
defer svg.MockIcon("octicon-alert")()
defer svg.MockIcon("octicon-stop")()
test := func(input, expected string) {
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
}
renderAttention := func(attention, icon string) string {
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
@@ -31,12 +36,6 @@ func TestAttention(t *testing.T) {
return tmpl
}
test := func(input, expected string) {
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
}
test(`
> [!NOTE]
> text
@@ -53,4 +52,7 @@ func TestAttention(t *testing.T) {
// legacy GitHub style
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
// edge case (it used to cause panic)
test(">\ntext", "<blockquote>\n</blockquote>\n<p>text</p>")
}

View File

@@ -383,18 +383,74 @@ func TestColorPreview(t *testing.T) {
}
}
func TestTaskList(t *testing.T) {
func TestMarkdownFrontmatter(t *testing.T) {
testcases := []struct {
testcase string
name string
input string
expected string
}{
{
"MapInFrontmatter",
`---
key1: val1
key2: val2
---
test
`,
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> key1, key2</summary><table>
<thead>
<tr>
<th>key1</th>
<th>key2</th>
</tr>
</thead>
<tbody>
<tr>
<td>val1</td>
<td>val2</td>
</tr>
</tbody>
</table>
</details><p>test</p>
`,
},
{
"ListInFrontmatter",
`---
- item1
- item2
---
test
`,
`- item1
- item2
<p>test</p>
`,
},
{
"StringInFrontmatter",
`---
anything
---
test
`,
`anything
<p>test</p>
`,
},
{
// data-source-position should take into account YAML frontmatter.
"ListAfterFrontmatter",
`---
foo: bar
---
- [ ] task 1`,
`<details><summary><i class="icon table"></i></summary><table>
`<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> foo</summary><table>
<thead>
<tr>
<th>foo</th>
@@ -414,9 +470,9 @@ foo: bar
}
for _, test := range testcases {
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
}
}

View File

@@ -16,7 +16,6 @@ import (
// RenderConfig represents rendering configuration for this file
type RenderConfig struct {
Meta markup.RenderMetaMode
Icon string
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
Lang string
yamlNode *yaml.Node
@@ -74,7 +73,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
type yamlRenderConfig struct {
Meta *string `yaml:"meta"`
Icon *string `yaml:"details_icon"`
Icon *string `yaml:"details_icon"` // deprecated, because there is no font icon, so no custom icon
TOC *string `yaml:"include_toc"`
Lang *string `yaml:"lang"`
}
@@ -96,10 +95,6 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
}
if cfg.Gitea.Icon != nil {
rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon))
}
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
rc.Lang = *cfg.Gitea.Lang
}
@@ -111,7 +106,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
return nil
}
func (rc *RenderConfig) toMetaNode() ast.Node {
func (rc *RenderConfig) toMetaNode(g *ASTTransformer) ast.Node {
if rc.yamlNode == nil {
return nil
}
@@ -119,7 +114,7 @@ func (rc *RenderConfig) toMetaNode() ast.Node {
case markup.RenderMetaAsTable:
return nodeToTable(rc.yamlNode)
case markup.RenderMetaAsDetails:
return nodeToDetails(rc.yamlNode, rc.Icon)
return nodeToDetails(g, rc.yamlNode)
default:
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
@@ -19,42 +20,36 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"empty", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "",
}, "",
},
{
"lang", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "test",
}, "lang: test",
},
{
"metatable", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "",
}, "gitea: table",
},
{
"metanone", &RenderConfig{
Meta: "none",
Icon: "table",
Lang: "",
}, "gitea: none",
},
{
"metadetails", &RenderConfig{
Meta: "details",
Icon: "table",
Lang: "",
}, "gitea: details",
},
{
"metawrong", &RenderConfig{
Meta: "details",
Icon: "table",
Lang: "",
}, "gitea: wrong",
},
@@ -62,7 +57,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
"toc", &RenderConfig{
TOC: "true",
Meta: "table",
Icon: "table",
Lang: "",
}, "include_toc: true",
},
@@ -70,14 +64,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
"tocfalse", &RenderConfig{
TOC: "false",
Meta: "table",
Icon: "table",
Lang: "",
}, "include_toc: false",
},
{
"toclang", &RenderConfig{
Meta: "table",
Icon: "table",
TOC: "true",
Lang: "testlang",
}, `
@@ -88,7 +80,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "testlang",
}, `
gitea:
@@ -98,7 +89,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang2", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "testlang",
}, `
lang: notright
@@ -109,7 +99,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang", &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "testlang",
}, `
gitea:
@@ -121,7 +110,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
Lang: "two",
Meta: "table",
TOC: "true",
Icon: "smiley",
}, `
lang: one
include_toc: true
@@ -137,7 +125,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got := &RenderConfig{
Meta: "table",
Icon: "table",
Lang: "",
}
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
@@ -145,18 +132,9 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
return
}
if got.Meta != tt.expected.Meta {
t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta)
}
if got.Icon != tt.expected.Icon {
t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
}
if got.Lang != tt.expected.Lang {
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
}
if got.TOC != tt.expected.TOC {
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
}
assert.Equal(t, tt.expected.Meta, got.Meta)
assert.Equal(t, tt.expected.Lang, got.Lang)
assert.Equal(t, tt.expected.TOC, got.TOC)
})
}
}

View File

@@ -115,6 +115,9 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read
// grab these nodes and make sure we adhere to the attention blockquote structure
firstParagraph := v.FirstChild()
if firstParagraph == nil {
return ast.WalkContinue, nil
}
g.applyElementDir(firstParagraph)
attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)

View File

@@ -1,4 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup_test

View File

@@ -7,9 +7,9 @@ import (
"context"
"fmt"
"net/url"
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
)
@@ -82,29 +82,32 @@ type HookProcReceiveRefResult struct {
HeadBranch string
}
func newInternalRequestAPIForHooks(ctx context.Context, hookName, ownerName, repoName string, opts HookOptions) *httplib.Request {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/%s/%s/%s", hookName, url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
// This "timeout" applies to http.Client's timeout: A Timeout of zero means no timeout.
// This "timeout" was previously set to `time.Duration(60+len(opts.OldCommitIDs))` seconds, but it caused unnecessary timeout failures.
// It should be good enough to remove the client side timeout, only respect the "ctx" and server side timeout.
req.SetReadWriteTimeout(0)
return req
}
// HookPreReceive check whether the provided commits are allowed
func HookPreReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) ResponseExtra {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
req := newInternalRequestAPIForHooks(ctx, "pre-receive", ownerName, repoName, opts)
_, extra := requestJSONResp(req, &ResponseText{})
return extra
}
// HookPostReceive updates services and users
func HookPostReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookPostReceiveResult, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
req := newInternalRequestAPIForHooks(ctx, "post-receive", ownerName, repoName, opts)
return requestJSONResp(req, &HookPostReceiveResult{})
}
// HookProcReceive proc-receive hook
func HookProcReceive(ctx context.Context, ownerName, repoName string, opts HookOptions) (*HookProcReceiveResult, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/proc-receive/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
req := newInternalRequestAPI(ctx, reqURL, "POST", opts)
req.SetReadWriteTimeout(time.Duration(60+len(opts.OldCommitIDs)) * time.Second)
req := newInternalRequestAPIForHooks(ctx, "proc-receive", ownerName, repoName, opts)
return requestJSONResp(req, &HookProcReceiveResult{})
}

View File

@@ -40,6 +40,10 @@ func NewInternalRequest(ctx context.Context, url, method string) *httplib.Reques
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
}
if !strings.HasPrefix(url, setting.LocalURL) {
log.Fatal("Invalid internal request URL: %q", url)
}
req := httplib.NewRequest(url, method).
SetContext(ctx).
Header("X-Real-IP", getClientIP()).

View File

@@ -26,6 +26,7 @@ type ConfigKey interface {
In(defaultVal string, candidates []string) string
String() string
Strings(delim string) []string
Bool() (bool, error)
MustString(defaultVal string) string
MustBool(defaultVal ...bool) bool

View File

@@ -169,12 +169,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
Protocol = HTTP
protocolCfg := sec.Key("PROTOCOL").String()
switch protocolCfg {
case "https":
Protocol = HTTPS
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
if sec.HasKey("ENABLE_ACME") {
@@ -183,6 +177,16 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
}
Protocol = HTTP
protocolCfg := sec.Key("PROTOCOL").String()
if protocolCfg != "https" && EnableAcme {
log.Fatal("ACME could only be used with HTTPS protocol")
}
switch protocolCfg {
case "https":
Protocol = HTTPS
if EnableAcme {
AcmeURL = sec.Key("ACME_URL").MustString("")
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
@@ -210,6 +214,9 @@ func loadServerFrom(rootCfg ConfigProvider) {
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
}
if AcmeEmail == "" {
log.Fatal("ACME Email is not set (ACME_EMAIL).")
}
} else {
CertFile = sec.Key("CERT_FILE").String()
KeyFile = sec.Key("KEY_FILE").String()

View File

@@ -43,9 +43,11 @@ var Service = struct {
ShowRegistrationButton bool
EnablePasswordSignInForm bool
ShowMilestonesDashboardPage bool
RequireSignInView bool
RequireSignInViewStrict bool
BlockAnonymousAccessExpensive bool
EnableNotifyMail bool
EnableBasicAuth bool
EnablePasskeyAuth bool
EnableReverseProxyAuth bool
EnableReverseProxyAuthAPI bool
EnableReverseProxyAutoRegister bool
@@ -158,9 +160,21 @@ func loadServiceFrom(rootCfg ConfigProvider) {
Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
// boolean values are considered as "strict"
var err error
Service.RequireSignInViewStrict, err = sec.Key("REQUIRE_SIGNIN_VIEW").Bool()
if s := sec.Key("REQUIRE_SIGNIN_VIEW").String(); err != nil && s != "" {
// non-boolean value only supports "expensive" at the moment
Service.BlockAnonymousAccessExpensive = s == "expensive"
if !Service.BlockAnonymousAccessExpensive {
log.Error("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s)
}
}
Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true)
Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true)
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool()
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()

View File

@@ -7,16 +7,14 @@ import (
"testing"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"github.com/gobwas/glob"
"github.com/stretchr/testify/assert"
)
func TestLoadServices(t *testing.T) {
oldService := Service
defer func() {
Service = oldService
}()
defer test.MockVariableValue(&Service)()
cfg, err := NewConfigProviderFromData(`
[service]
@@ -48,10 +46,7 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b
}
func TestLoadServiceVisibilityModes(t *testing.T) {
oldService := Service
defer func() {
Service = oldService
}()
defer test.MockVariableValue(&Service)()
kases := map[string]func(){
`
@@ -130,3 +125,33 @@ ALLOWED_USER_VISIBILITY_MODES = public, limit, privated
})
}
}
func TestLoadServiceRequireSignInView(t *testing.T) {
defer test.MockVariableValue(&Service)()
cfg, err := NewConfigProviderFromData(`
[service]
`)
assert.NoError(t, err)
loadServiceFrom(cfg)
assert.False(t, Service.RequireSignInViewStrict)
assert.False(t, Service.BlockAnonymousAccessExpensive)
cfg, err = NewConfigProviderFromData(`
[service]
REQUIRE_SIGNIN_VIEW = true
`)
assert.NoError(t, err)
loadServiceFrom(cfg)
assert.True(t, Service.RequireSignInViewStrict)
assert.False(t, Service.BlockAnonymousAccessExpensive)
cfg, err = NewConfigProviderFromData(`
[service]
REQUIRE_SIGNIN_VIEW = expensive
`)
assert.NoError(t, err)
loadServiceFrom(cfg)
assert.False(t, Service.RequireSignInViewStrict)
assert.True(t, Service.BlockAnonymousAccessExpensive)
}

View File

@@ -27,9 +27,10 @@ type PullRequest struct {
Comments int `json:"comments"`
// number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
ReviewComments int `json:"review_comments"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
ChangedFiles int `json:"changed_files"`
Additions *int `json:"additions,omitempty"`
Deletions *int `json:"deletions,omitempty"`
ChangedFiles *int `json:"changed_files,omitempty"`
HTMLURL string `json:"html_url"`
DiffURL string `json:"diff_url"`

View File

@@ -23,9 +23,11 @@ type AccessToken struct {
type AccessTokenList []*AccessToken
// CreateAccessTokenOption options when create access token
// swagger:model CreateAccessTokenOption
type CreateAccessTokenOption struct {
// required: true
Name string `json:"name" binding:"Required"`
// example: ["all", "read:activitypub","read:issue", "write:misc", "read:notification", "read:organization", "read:package", "read:repository", "read:user"]
Scopes []string `json:"scopes"`
}

View File

@@ -2,6 +2,7 @@
applet
application.linux-arm64
application.linux-armv6hf
application.linux-riscv64
application.linux32
application.linux64
application.windows32

View File

@@ -2699,6 +2699,7 @@ branch.restore_success = Branch "%s" has been restored.
branch.restore_failed = Failed to restore branch "%s".
branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted.
branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted.
branch.default_branch_not_exist = Default branch "%s" does not exist.
branch.restore = Restore Branch "%s"
branch.download = Download Branch "%s"
branch.rename = Rename Branch "%s"

View File

@@ -1943,8 +1943,8 @@ pulls.delete.title=删除此合并请求?
pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong>
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %s %d 个提交
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %s %d 个提交
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %[2]s %[1]d 个提交
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %[2]s %[1]d 个提交
pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
pulls.upstream_diverging_merge=同步派生

View File

@@ -51,7 +51,7 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://rust-lang.github.io/rfcs/2789-sparse-index.html
func RepositoryConfig(ctx *context.Context) {
ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
}
func EnumeratePackageVersions(ctx *context.Context) {

View File

@@ -126,7 +126,7 @@ func apiUnauthorizedError(ctx *context.Context) {
// ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled)
func ReqContainerAccess(ctx *context.Context) {
if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) {
if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) {
apiUnauthorizedError(ctx)
}
}
@@ -152,7 +152,7 @@ func Authenticate(ctx *context.Context) {
u := ctx.Doer
packageScope := auth_service.GetAccessScope(ctx.Data)
if u == nil {
if setting.Service.RequireSignInView {
if setting.Service.RequireSignInViewStrict {
apiUnauthorizedError(ctx)
return
}

View File

@@ -8,7 +8,6 @@ import (
"strings"
packages_model "code.gitea.io/gitea/models/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven"
)
// MetadataResponse https://maven.apache.org/ref/3.2.5/maven-repository-metadata/repository-metadata.html
@@ -22,7 +21,7 @@ type MetadataResponse struct {
}
// pds is expected to be sorted ascending by CreatedUnix
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
func createMetadataResponse(pds []*packages_model.PackageDescriptor, groupID, artifactID string) *MetadataResponse {
var release *packages_model.PackageDescriptor
versions := make([]string, 0, len(pds))
@@ -35,11 +34,9 @@ func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataRe
latest := pds[len(pds)-1]
metadata := latest.Metadata.(*maven_module.Metadata)
resp := &MetadataResponse{
GroupID: metadata.GroupID,
ArtifactID: metadata.ArtifactID,
GroupID: groupID,
ArtifactID: artifactID,
Latest: latest.Version.Version,
Version: versions,
}

View File

@@ -84,16 +84,20 @@ func handlePackageFile(ctx *context.Context, serveContent bool) {
}
func serveMavenMetadata(ctx *context.Context, params parameters) {
// /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
if errors.Is(err, util.ErrNotExist) {
pvs, err = packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
}
// path pattern: /com/foo/project/maven-metadata.xml[.md5/.sha1/.sha256/.sha512]
// in case there are legacy package names ("GroupID-ArtifactID") we need to check both, new packages always use ":" as separator("GroupID:ArtifactID")
pvsLegacy, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageNameLegacy())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, params.toInternalPackageName())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pvs = append(pvsLegacy, pvs...)
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, packages_model.ErrPackageNotExist)
return
@@ -110,7 +114,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
})
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds, params.GroupID, params.ArtifactID))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return

View File

@@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
})
}
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
multipartFile, _, err := ctx.Req.FormFile(formKey)
if err != nil && !errors.Is(err, http.ErrMissingFile) {
return nil, err
}
if multipartFile != nil {
return multipartFile, nil
}
content := ctx.Req.FormValue(formKey)
if content == "" {
return nil, nil
}
return io.NopCloser(strings.NewReader(ctx.Req.FormValue(formKey))), nil
}
// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
func UploadPackageFile(ctx *context.Context) {
packageScope := ctx.PathParam("scope")
packageName := ctx.PathParam("name")
@@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
packageVersion := v.Core().String()
file, _, err := ctx.Req.FormFile("source-archive")
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
file, err := formFileOptionalReadCloser(ctx, "source-archive")
if file == nil || err != nil {
apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
return
}
defer file.Close()
@@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
}
defer buf.Close()
var mr io.Reader
metadata := ctx.Req.FormValue("metadata")
if metadata != "" {
mr = strings.NewReader(metadata)
mr, err := formFileOptionalReadCloser(ctx, "metadata")
if err != nil {
apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
return
}
if mr != nil {
defer mr.Close()
}
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)

View File

@@ -356,7 +356,7 @@ func reqToken() func(ctx *context.APIContext) {
func reqExploreSignIn() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned {
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
}
}
@@ -874,7 +874,7 @@ func Routes() *web.Router {
m.Use(apiAuth(buildAuthGroup()))
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
SignInRequired: setting.Service.RequireSignInView,
SignInRequired: setting.Service.RequireSignInViewStrict,
}))
addActionsRoutes := func(

View File

@@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
if issue.MilestoneID > 0 {
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
return
}
} else {
issue.Milestone = nil
}
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
return

View File

@@ -694,6 +694,11 @@ func EditPullRequest(ctx *context.APIContext) {
issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
return
}
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
return
@@ -1638,7 +1643,9 @@ func GetPullRequestFiles(ctx *context.APIContext) {
apiFiles := make([]*api.ChangedFile, 0, limit)
for i := start; i < start+limit; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
// refs/pull/1/head stores the HEAD commit ID, allowing all related commits to be found in the base repository.
// The head repository might have been deleted, so we should not rely on it here.
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
}
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)

View File

@@ -12,7 +12,6 @@ import (
"strings"
"time"
actions_model "code.gitea.io/gitea/models/actions"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
@@ -1050,7 +1049,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e
ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
return err
}
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
}
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)

View File

@@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
// Get last change information.
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
@@ -432,7 +432,7 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get commit count - wiki revisions
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
page := ctx.FormInt("page")
if page <= 1 {
@@ -442,7 +442,7 @@ func ListPageRevisions(ctx *context.APIContext) {
// get Commit Count
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: "master",
Revision: ctx.Repo.Repository.DefaultWikiBranch,
File: pageFilename,
Page: page,
})
@@ -486,7 +486,7 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit)
return nil, nil
}
commit, err := wikiRepo.GetBranchCommit("master")
commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound(err)

View File

@@ -0,0 +1,92 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"context"
"net/http"
"strings"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"github.com/go-chi/chi/v5"
)
func BlockExpensive() func(next http.Handler) http.Handler {
if !setting.Service.BlockAnonymousAccessExpensive {
return nil
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ret := determineRequestPriority(req.Context())
if !ret.SignedIn {
if ret.Expensive || ret.LongPolling {
http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther)
return
}
}
next.ServeHTTP(w, req)
})
}
}
func isRoutePathExpensive(routePattern string) bool {
if strings.HasPrefix(routePattern, "/user/") || strings.HasPrefix(routePattern, "/login/") {
return false
}
expensivePaths := []string{
// code related
"/{username}/{reponame}/archive/",
"/{username}/{reponame}/blame/",
"/{username}/{reponame}/commit/",
"/{username}/{reponame}/commits/",
"/{username}/{reponame}/graph",
"/{username}/{reponame}/media/",
"/{username}/{reponame}/raw/",
"/{username}/{reponame}/src/",
// issue & PR related (no trailing slash)
"/{username}/{reponame}/issues",
"/{username}/{reponame}/{type:issues}",
"/{username}/{reponame}/pulls",
"/{username}/{reponame}/{type:pulls}",
"/{username}/{reponame}/{type:issues|pulls}", // for 1.23 only
// wiki
"/{username}/{reponame}/wiki/",
// activity
"/{username}/{reponame}/activity/",
}
for _, path := range expensivePaths {
if strings.HasPrefix(routePattern, path) {
return true
}
}
return false
}
func isRoutePathForLongPolling(routePattern string) bool {
return routePattern == "/user/events"
}
func determineRequestPriority(ctx context.Context) (ret struct {
SignedIn bool
Expensive bool
LongPolling bool
},
) {
dataStore := middleware.GetContextData(ctx)
chiRoutePath := chi.RouteContext(ctx).RoutePattern()
if _, ok := dataStore[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
ret.SignedIn = true
} else {
ret.Expensive = isRoutePathExpensive(chiRoutePath)
ret.LongPolling = isRoutePathForLongPolling(chiRoutePath)
}
return ret
}

View File

@@ -0,0 +1,30 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBlockExpensive(t *testing.T) {
cases := []struct {
expensive bool
routePath string
}{
{false, "/user/xxx"},
{false, "/login/xxx"},
{true, "/{username}/{reponame}/archive/xxx"},
{true, "/{username}/{reponame}/graph"},
{true, "/{username}/{reponame}/src/xxx"},
{true, "/{username}/{reponame}/wiki/xxx"},
{true, "/{username}/{reponame}/activity/xxx"},
}
for _, c := range cases {
assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath)
}
assert.True(t, isRoutePathForLongPolling("/user/events"))
}

View File

@@ -156,7 +156,7 @@ func Install(ctx *context.Context) {
form.DisableRegistration = setting.Service.DisableRegistration
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
form.EnableCaptcha = setting.Service.EnableCaptcha
form.RequireSignInView = setting.Service.RequireSignInView
form.RequireSignInView = setting.Service.RequireSignInViewStrict
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking

View File

@@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
ownerName := ctx.PathParam(":owner")
repoName := ctx.PathParam(":repo")
mode := perm.AccessMode(ctx.FormInt("mode"))
verb := ctx.FormString("verb")
// Set the basic parts of the results to return
results := private.ServCommandResults{
@@ -286,7 +287,7 @@ func ServCommand(ctx *context.PrivateContext) {
repo.IsPrivate ||
owner.Visibility.IsPrivate() ||
(user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey
setting.Service.RequireSignInView) {
setting.Service.RequireSignInViewStrict) {
if key.Type == asymkey_model.KeyTypeDeploy {
if deployKey.Mode < mode {
ctx.JSON(http.StatusUnauthorized, private.Response{
@@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
return
}
} else {
// Because of the special ref "refs/for" we will need to delay write permission check
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == "git-receive-pack" {
mode = perm.AccessModeRead
}

View File

@@ -169,6 +169,7 @@ func prepareSignInPageData(ctx *context.Context) {
ctx.Data["PageIsLogin"] = true
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.SetCaptchaData(ctx)

View File

@@ -46,6 +46,7 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
@@ -145,6 +146,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
@@ -235,6 +237,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
// use this to set the right link into the signIn and signUp templates in the link_account template
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"

View File

@@ -248,7 +248,7 @@ func AuthorizeOAuth(ctx *context.Context) {
}, form.RedirectURI)
return
}
if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
if err := ctx.Session.Set("CodeChallenge", form.CodeChallenge); err != nil {
handleAuthorizeError(ctx, AuthorizeError{
ErrorCode: ErrorCodeServerError,
ErrorDescription: "cannot set code challenge",

View File

@@ -50,6 +50,11 @@ func WebAuthn(ctx *context.Context) {
// WebAuthnPasskeyAssertion submits a WebAuthn challenge for the passkey login to the browser
func WebAuthnPasskeyAssertion(ctx *context.Context) {
if !setting.Service.EnablePasskeyAuth {
ctx.Error(http.StatusForbidden)
return
}
assertion, sessionData, err := wa.WebAuthn.BeginDiscoverableLogin()
if err != nil {
ctx.ServerError("webauthn.BeginDiscoverableLogin", err)
@@ -66,6 +71,11 @@ func WebAuthnPasskeyAssertion(ctx *context.Context) {
// WebAuthnPasskeyLogin handles the WebAuthn login process using a Passkey
func WebAuthnPasskeyLogin(ctx *context.Context) {
if !setting.Service.EnablePasskeyAuth {
ctx.Error(http.StatusForbidden)
return
}
sessionData, okData := ctx.Session.Get("webauthnPasskeyAssertion").(*webauthn.SessionData)
if !okData || sessionData == nil {
ctx.ServerError("ctx.Session.Get", errors.New("not in WebAuthn session"))

View File

@@ -4,26 +4,12 @@
package web
import (
"net/http"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/context"
)
func addOwnerRepoGitHTTPRouters(m *web.Router) {
reqGitSignIn := func(ctx *context.Context) {
if !setting.Service.RequireSignInView {
return
}
// rely on the results of Contexter
if !ctx.IsSigned {
// TODO: support digit auth - which would be Authorization header with digit
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
ctx.Error(http.StatusUnauthorized)
}
}
m.Group("/{username}/{reponame}", func() {
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
@@ -36,5 +22,5 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) {
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
}, optSignInIgnoreCsrf, reqGitSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
}

View File

@@ -6,6 +6,7 @@ package actions
import (
"bytes"
stdCtx "context"
"errors"
"fmt"
"net/http"
"slices"
@@ -77,7 +78,11 @@ func List(ctx *context.Context) {
return
} else if !empty {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
ctx.NotFound("GetBranchCommit", err)
return
} else if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}

View File

@@ -903,7 +903,7 @@ func Run(ctx *context_module.Context) {
}
// cancel running jobs of the same workflow
if err := actions_model.CancelPreviousJobs(
if err := actions_service.CancelPreviousJobs(
ctx,
run.RepoID,
run.Ref,

View File

@@ -8,6 +8,7 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/services/context"
@@ -52,12 +53,26 @@ func Activity(ctx *context.Context) {
ctx.Data["DateUntil"] = timeUntil
ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string))
canReadCode := ctx.Repo.CanRead(unit.TypeCode)
if canReadCode {
// GetActivityStats needs to read the default branch to get some information
branchExist, _ := git.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch)
if !branchExist {
ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch)
ctx.NotFound("", nil)
return
}
}
var err error
if ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom,
// TODO: refactor these arguments to a struct
ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom,
ctx.Repo.CanRead(unit.TypeReleases),
ctx.Repo.CanRead(unit.TypeIssues),
ctx.Repo.CanRead(unit.TypePullRequests),
ctx.Repo.CanRead(unit.TypeCode)); err != nil {
canReadCode,
)
if err != nil {
ctx.ServerError("GetActivityStats", err)
return
}

View File

@@ -34,7 +34,7 @@ func CodeFrequencyData(ctx *context.Context) {
ctx.Status(http.StatusAccepted)
return
}
ctx.ServerError("GetCodeFrequencyData", err)
ctx.ServerError("GetContributorStats", err)
} else {
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
}

View File

@@ -405,7 +405,6 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo {
ctx.ServerError("OpenRepository", err)
return nil
}
defer ci.HeadGitRepo.Close()
} else {
ctx.NotFound("ParseCompareInfo", nil)
return nil
@@ -708,7 +707,7 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
func CompareDiff(ctx *context.Context) {
ci := ParseCompareInfo(ctx)
defer func() {
if ci != nil && ci.HeadGitRepo != nil {
if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
ci.HeadGitRepo.Close()
}
}()

View File

@@ -127,7 +127,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
// Only public pull don't need auth.
isPublicPull := repoExist && !repo.IsPrivate && isPull
var (
askAuth = !isPublicPull || setting.Service.RequireSignInView
askAuth = !isPublicPull || setting.Service.RequireSignInViewStrict
environ []string
)

View File

@@ -418,6 +418,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
continue
}
issue.MilestoneID = milestoneID
if milestoneID > 0 {
var err error
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
ctx.ServerError("GetMilestoneByRepoID", err)
return
}
} else {
issue.Milestone = nil
}
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
ctx.ServerError("ChangeMilestoneAssign", err)
return

View File

@@ -1263,7 +1263,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
ci := ParseCompareInfo(ctx)
defer func() {
if ci != nil && ci.HeadGitRepo != nil {
if !ctx.Repo.PullRequest.SameRepo && ci != nil && ci.HeadGitRepo != nil {
ci.HeadGitRepo.Close()
}
}()

View File

@@ -4,12 +4,10 @@
package repo
import (
"errors"
"net/http"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
const (
@@ -26,16 +24,3 @@ func RecentCommits(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplRecentCommits)
}
// RecentCommitsData returns JSON of recent commits data
func RecentCommitsData(ctx *context.Context) {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
ctx.Status(http.StatusAccepted)
return
}
ctx.ServerError("RecentCommitsData", err)
} else {
ctx.JSON(http.StatusOK, contributorStats["total"].Weeks)
}
}

View File

@@ -12,7 +12,6 @@ import (
"time"
"code.gitea.io/gitea/models"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
@@ -902,7 +901,7 @@ func SettingsPost(ctx *context.Context) {
return
}
if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
if err := actions_service.CleanRepoScheduleTasks(ctx, repo); err != nil {
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
}

View File

@@ -9,7 +9,6 @@ import (
"image"
"io"
"path"
"slices"
"strings"
git_model "code.gitea.io/gitea/models/git"
@@ -79,7 +78,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
if workFlowErr != nil {
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
}
} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
} else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) {
if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
if len(warnings) > 0 {

View File

@@ -285,23 +285,23 @@ func Routes() *web.Router {
mid = append(mid, repo.GetActiveStopwatch)
mid = append(mid, goGet)
others := web.NewRouter()
others.Use(mid...)
registerRoutes(others)
routes.Mount("", others)
webRoutes := web.NewRouter()
webRoutes.Use(mid...)
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive())
routes.Mount("", webRoutes)
return routes
}
var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
// registerRoutes register routes
func registerRoutes(m *web.Router) {
// registerWebRoutes register routes
func registerWebRoutes(m *web.Router) {
// required to be signed in or signed out
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
// optional sign in (if signed in, use the user as doer, if not, no doer)
optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict})
optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView})
validation.AddBindingRules()
@@ -1454,6 +1454,8 @@ func registerRoutes(m *web.Router) {
m.Group("/{username}/{reponame}/activity", func() {
m.Get("", repo.Activity)
m.Get("/{period}", repo.Activity)
m.Group("", func() {
m.Group("/contributors", func() {
m.Get("", repo.Contributors)
m.Get("/data", repo.ContributorsData)
@@ -1464,10 +1466,11 @@ func registerRoutes(m *web.Router) {
})
m.Group("/recent-commits", func() {
m.Get("", repo.RecentCommits)
m.Get("/data", repo.RecentCommitsData)
m.Get("/data", repo.CodeFrequencyData) // "recent-commits" also uses the same data as "code-frequency"
})
}, reqRepoCodeReader)
},
optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases),
optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeCode, unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases),
context.RepoRef(), repo.MustBeNotEmpty,
)
// end "/{username}/{reponame}/activity"

View File

@@ -10,10 +10,12 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"
)
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time
@@ -32,6 +34,24 @@ func StopEndlessTasks(ctx context.Context) error {
})
}
func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.ActionRunJob) {
if len(jobs) > 0 {
CreateCommitStatus(ctx, jobs...)
}
}
func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error {
jobs, err := actions_model.CancelPreviousJobs(ctx, repoID, ref, workflowID, event)
notifyWorkflowJobStatusUpdate(ctx, jobs)
return err
}
func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error {
jobs, err := actions_model.CleanRepoScheduleTasks(ctx, repo)
notifyWorkflowJobStatusUpdate(ctx, jobs)
return err
}
func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
tasks, err := db.Find[actions_model.ActionTask](ctx, opts)
if err != nil {
@@ -67,7 +87,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
remove()
}
CreateCommitStatus(ctx, jobs...)
notifyWorkflowJobStatusUpdate(ctx, jobs)
return nil
}

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