Compare commits

..

205 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
Lunny Xiao
de7026528b
Release of Gitea 1.23.4 (#33621)
---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
2025-02-19 01:16:26 +00:00
Lunny Xiao
ee3f5e8fac
fix: add missing locale (#33641) (#33642) 2025-02-19 00:57:14 +00:00
Giteabot
b2707bcd18
Make actions URL in commit status webhooks absolute (#33620) (#33632)
Backport #33620 by lunny

Fix #33603

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-18 02:46:08 +00:00
Giteabot
0512b02b01
Fix project issues list and counting (#33594) (#33619)
Backport #33594 by lunny

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-18 00:59:32 +08:00
Giteabot
99545ae2fd
Fix mirror bug (#33597) (#33607)
Backport #33597 by @ericLemanissier

follows-up be4e961240

This is the same modification as
be4e961240
but for force-pushes.
It is needed, because `git fetch` reveals force pushes for github
mirrors:
```
$ git fetch --tags origin
remote: Enumerating objects: 22, done.
remote: Counting objects: 100% (22/22), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 9 (delta 5), reused 8 (delta 5), pack-reused 0 (from 0)
Unpacking objects: 100% (9/9), 1.70 KiB | 12.00 KiB/s, done.
From https://github.com/conan-io/conan-center-index
   729f0f1b8f..48184eddeb  refs/pull/26595/head  -> refs/pull/26595/head
 + 0c31ab60a3...1283cca9e7 refs/pull/26595/merge -> refs/pull/26595/merge  (forced update)
```

Fix https://github.com/go-gitea/gitea/issues/33200

PS: I did not test the modification, but it is the exact same change as
the last hunk in
https://github.com/go-gitea/gitea/pull/33224/files#diff-bb5cdb90db0f0e7f6716c0e6d0b9cbb67f08d82052b03ab3a7b5e23a1d76aed7
, just moved to the previous case of the switch

Co-authored-by: ericLemanissier <ericLemanissier@users.noreply.github.com>
2025-02-15 21:17:34 -08:00
Giteabot
7697df9f93
Use default Git timeout when checking repo health (#33593) (#33598)
Backport #33593 by @Zettat123

Use `git.timeout.DEFAULT` configuration instead of 60 seconds.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2025-02-14 15:38:55 +00:00
wxiaoguang
d17f8ffcc1
Fix PR's target branch dropdown (#33589) (#33591)
Backport #33589
2025-02-14 08:38:33 +00:00
Giteabot
5e9cc919cf
Performance optimization for pull request files loading comments attachments (#33585) (#33592)
Backport #33585 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-02-14 00:06:46 -08:00
Giteabot
cc6ec56738
Only show the latest version in the Arch index (#33262) (#33580)
Backport #33262 by ExplodingDragon

Only show the latest version of the package in the arch repo.

closes #33534

Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-13 20:02:28 +08:00
Giteabot
76bd60fc1d
Fix various problems (artifact order, api empty slice, assignee check, fuzzy prompt, mirror proxy, adopt git) (#33569) (#33577)
Backport #33569 by @wxiaoguang

* Make artifact list output has stable order
* Fix #33506
* Fix #33521
* Fix #33288
* Fix #33196
* Fix #33561

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-13 07:33:11 +08:00
wxiaoguang
744f7c8200
Skip deletion error for action artifacts (#33476) (#33568)
Fix #33567
2025-02-13 03:27:37 +08:00
Lunny Xiao
da33b708af
Add a transaction to pickTask (#33543) (#33563)
Backport #33543 

In the old `pickTask`, when getting secrets or variables failed, the
task could get stuck in the `running` status (task status is `running`
but the runner did not fetch the task). To fix this issue, these steps
should be in one transaction.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-12 11:53:56 +08:00
wxiaoguang
8fa3925874
Fix context usage (#33554) (#33557)
Backport #33554
2025-02-11 19:46:27 +08:00
Lunny Xiao
7794ff0874
Enhance routers for the Actions runner operations (#33549) (#33555)
Backport #33549 

- Find the runner before deleting
- Move the main logic from `routers/web/repo/setting/runners.go` to
`routers/web/shared/actions/runners.go`.

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-11 15:25:56 +08:00
Lunny Xiao
7c17d0a73e
Enhance routers for the Actions variable operations (#33547) (#33553)
Backport #33547

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
2025-02-11 03:52:09 +00:00
Giteabot
a014d071e4
Rework suggestion backend (#33538) (#33546)
Backport #33538 by @lunny

Fix #33522 

The suggestion backend logic now is

- If the keyword is empty, returned the latest 5 issues/prs with index
desc order
- If the keyword is digital, find all issues/prs which `index` has a
prefix with that, with index asc order
- If the keyword is non-digital or if the queried records less than 5,
searching issues/prs title with a `like`, with index desc order

## Empty keyword
<img width="310" alt="image"
src="https://github.com/user-attachments/assets/1912c634-0d98-4eeb-8542-d54240901f77"
/>

## Digital
<img width="479" alt="image"
src="https://github.com/user-attachments/assets/0356a936-7110-4a24-b21e-7400201bf9b8"
/>

## Digital and title contains the digital
<img width="363" alt="image"
src="https://github.com/user-attachments/assets/6e12f908-28fe-48de-8ccc-09cbeab024d4"
/>

## non-Digital
<img width="435" alt="image"
src="https://github.com/user-attachments/assets/2722bb53-baa2-4d67-a224-522a65f73856"
/>
<img width="477" alt="image"
src="https://github.com/user-attachments/assets/06708dd9-80d1-4a88-b32b-d29072dd1ba6"
/>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-11 01:22:39 +08:00
Lunny Xiao
312565e3c2
Add changelog for 1.23.3 (#33515)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2025-02-06 12:32:13 +08:00
Lunny Xiao
58daaf66e8
Fix a bug caused by status webhook template (#33512)
Fix #33511
2025-02-06 08:48:22 +08:00
Lunny Xiao
f076ada601
Add changelog for 1.23.2 (#33494)
Wait 
- #33499
- #33493
- #33503

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-06 02:09:17 +08:00
Lunny Xiao
92436b8b2a
add timetzdata build tag to binary releases (#33463) (#33503)
Backport #33463

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-05 19:06:58 +08:00
Giteabot
2df7d0835a
Fix unnecessary comment when moving issue on the same project column (#33496) (#33499)
Backport #33496 by @lunny

Fix #33482

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-02-05 04:41:48 +00:00
Lunny Xiao
200cb6140d
Fix commit status events (#33320) (#33493)
Fix #32873
Fix #33201
~Fix #33244~
~Fix #33302~

depends on ~#33396~
backport #33320
2025-02-05 11:35:47 +08:00
Giteabot
2746c6f1aa
Correct bot label vertical-align (#33477) (#33480) 2025-02-02 15:08:59 -05:00
Lunny Xiao
23971a77a0
Add tests for webhook and fix some webhook bugs (#33396) (#33442)
This PR created a mock webhook server in the tests and added integration
tests for generic webhooks.
It also fixes bugs in package webhooks and pull request comment
webhooks.

This also corrected an error on the package webhook. The previous
implementation uses a `User` struct as an organization, now it has been
corrected but it will not be consistent with the previous
implementation, some fields which not belong to the organization have
been removed.

Backport #33396
Backport part of #33337
2025-02-02 14:44:50 +08:00
Giteabot
ebac324ff2
Fix GetCommitBranchStart bug (#33298) (#33421)
Backport #33298 by Zettat123

Fix #33265
Fix #33370

This PR also fixes some bugs in `TestGitGeneral`.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-02-01 09:43:10 +01:00
Giteabot
7df1204795
Fix SSH LFS memory usage (#33455) (#33460)
Backport #33455 by wxiaoguang

Fix #33448

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-31 11:30:16 +00:00
Giteabot
159544a950
Revert empty lfs ref name (#33454) (#33457)
Backport #33454 by wxiaoguang

Fix #33453

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-31 10:27:23 +00:00
Giteabot
9780da583d
Fix issue sidebar dropdown keyboard support (#33447) (#33450)
Backport #33447 by wxiaoguang

Just a quick fix, fix #33444

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-31 10:11:51 +08:00
wxiaoguang
a8eaf43f97
Fix user avatar (#33439) 2025-01-30 17:11:13 +08:00
Giteabot
b6fd8741ee
Fix system admin cannot fork or get private fork with API (#33401) (#33417)
Backport #33401 by @lunny

Fix #33368

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-01-27 18:43:16 +00:00
Giteabot
2674d27fb8
Add pubdate for repository rss and add some tests (#33411) (#33416)
Backport #33411 by @lunny

Fix #33291

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-01-27 17:42:47 +00:00
Giteabot
6f3837284d
Fix flex width (#33414) (#33418)
Backport #33414 by wxiaoguang

Fix #33409

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-27 17:16:21 +00:00
wxiaoguang
c30f4f4be5
Fix issue suggestion bug (#33389) (#33391)
Backport #33389
2025-01-27 19:11:13 +08:00
Giteabot
4578288ea3
Use ProtonMail/go-crypto to replace keybase/go-crypto (#33402) (#33410)
Backport #33402 by wxiaoguang

Fix #33400

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-27 02:50:00 +00:00
Giteabot
826fffb59e
Add missed auto merge feed message on dashboard (#33309) (#33405)
Backport #33309 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-01-26 18:57:45 +00:00
Giteabot
2196ba5e42
Clone button enhancements (#33362) (#33404)
Backport #33362 by @silverwind

- Add box-shadow to default tippy theme
- Make colors for tabs match the ones from `.ui.tabular.menu`
- Remove tippy arrow and slightly offset tooltip closer to the button
- Fix setting of `aria-haspopup` when default role is used with tippy

<img width="335" alt="image"
src="https://github.com/user-attachments/assets/8633ebac-a43f-457a-86bd-7a88a83519ee"
/>

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-26 19:20:57 +01:00
Giteabot
12347f07ae
Repo homepage styling tweaks (#33289) (#33381)
Backport #33289 by @silverwind

Reduce it to a value that results in `.repo-home-sidebar-top` and
`.repo-home-sidebar-bottom` having 240px content width, the same as
GitHub.

Co-authored-by: silverwind <me@silverwind.io>
2025-01-24 13:41:01 -05:00
silverwind
a3c5358d35
Update katex to latest version (#33361)
Partial backport of https://github.com/go-gitea/gitea/pull/33359.
Upgrade katex because there were a number of security problems fixed in
recent versions.
2025-01-23 01:21:58 +01:00
silverwind
987d014468
Update go tool dependencies (#32916) (#33355)
Clean cherry-pick of https://github.com/go-gitea/gitea/pull/32916.
Update all go tool dependencies to latest version.
2025-01-22 11:37:47 -05:00
Giteabot
e08eed9040
Fix code button alignment (#33345) (#33351)
Backport #33345 by silverwind

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-22 15:18:34 +08:00
wxiaoguang
4ffa49aa04
Make issue suggestion work for all editors (#33340) (#33342)
Backport #33340

And do not handle special keys when the text-expander popup exists
2025-01-21 21:09:37 +08:00
Giteabot
72837530bf
Fix issue count (#33338) (#33341)
Backport #33338 by wxiaoguang

Fix #33336

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-21 11:18:22 +00:00
wxiaoguang
eef635523a
Make tracked time representation display as hours (#33315) (#33334)
Try to backport #33315, the only trivial conflict is in the helper
functions map in the helper.go

Fix #33333

Co-authored-by: Sysoev, Vladimir <i@vsysoev.ru>
2025-01-21 06:49:58 +08:00
wxiaoguang
8f45a11919
Improve sync fork behavior (#33319) (#33332)
Backport #33319
Fix #33271

The only conflict is `reqctx` in
`services/repository/merge_upstream.go`, which could keep using
`context.Context` in 1.23
2025-01-20 07:50:38 +00:00
Giteabot
e72d001708
Fix Account linking page (#33325) (#33327)
Backport #33325 by CrimsonEdgeHope

Fix password form missing whilst linking account even with
`ENABLE_PASSWORD_SIGNIN_FORM = true`.

Remove redundant empty box in account linking sign up page when
`LinkAccountMode` is true.

Co-authored-by: CrimsonEdgeHope <92579614+CrimsonEdgeHope@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-19 13:02:33 +00:00
wxiaoguang
8d9ea68f19
Fix push message behavior (#33215) (#33317)
Backport #33215

Manually resolved "reqctx" conflict

---------

Co-authored-by: Chai-Shi <changchaishi@gmail.com>
2025-01-19 10:48:28 +08:00
wxiaoguang
bf664c2e85
Trivial fixes (#33304) (#33312)
Backport #33304

The only conflict is caused by `templates/shared/issueicon.tmpl`
2025-01-18 03:09:17 +08:00
wxiaoguang
52d298890b
Fix "stop time tracking button" on navbar (#33084) (#33300)
Backport #33084 (no conflict)
Fix #33299, and remove incorrect translations
2025-01-17 04:48:31 +08:00
wxiaoguang
c09e43acf5
Fix closed dependency title (#33285) (#33287)
Backport #33285
2025-01-15 16:05:01 +00:00
Giteabot
3b4af01633
Fix sidebar milestone link (#33269) (#33272)
Backport #33269 by wxiaoguang

Fix  #33266

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-15 00:19:37 +00:00
Giteabot
b4e2d5e8ee
Add a confirm dialog for "sync fork" (#33270) (#33273)
Backport #33270 by @wxiaoguang

Try to quickly fix #33264

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-15 00:51:17 +02:00
Giteabot
2984a7c121
Fix missing license when sync mirror (#33255) (#33258)
Backport #33255 by yp05327

Fix #33222

Co-authored-by: yp05327 <576951401@qq.com>
2025-01-14 06:26:27 +00:00
wxiaoguang
80cc87b3d8
Fix tag route and empty repo (#33253) 2025-01-14 14:01:30 +08:00
Giteabot
10b6047498
Fix upload file form (#33230) (#33233)
Backport #33230 by @wxiaoguang

Fix #33228

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-13 01:49:56 +02:00
Giteabot
2c47b06869
Fix mirror bug (#33224) (#33225)
Backport #33224 by lunny

Fix #33200

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-12 11:11:02 +00:00
Giteabot
31f2a325dc
fix(cache): cache test triggered by non memory cache (#33220) (#33221)
Backport #33220 by TheFox0x7

Change SlowCacheThreshold to 30 milliseconds so it doesn't trigger on
non memory cache

Closes: https://github.com/go-gitea/gitea/issues/33190
Closes: https://github.com/go-gitea/gitea/issues/32657

Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
2025-01-12 09:19:37 +08:00
Lunny Xiao
fcbbc24cc4
Change log for 1.23.1 (#33191) 2025-01-10 16:06:45 +08:00
wxiaoguang
1454e1b6eb
Fix editor markdown not incrementing in a numbered list (#33187) (#33193)
Backport #33187 (no conflict)

Co-authored-by: Harry Vince <47283812+harryvince@users.noreply.github.com>
2025-01-10 16:00:22 +08:00
Giteabot
d70348836b
Fix sync fork for consistency (#33147) (#33192)
Backport #33147 by changchaishi

Fixes #33145

An integration test could be added.

---------

Co-authored-by: Chai-Shi <changchaishi@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-10 15:11:45 +08:00
Giteabot
940a930d13
Use updated path to s6-svscan after alpine upgrade (#33185) (#33188)
Backport #33185 by @techknowlogick

Fix #33163

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-10 02:48:52 +00:00
Giteabot
45d21a0d5c
Fix raw file API ref handling (#33172) (#33189)
Backport #33172 by wxiaoguang

Fix #33164 and add more tests

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-10 02:41:28 +00:00
Giteabot
15ad001aef
Fix branch dropdown not display ref name (#33159) (#33183)
Backport #33159 by @yp05327

Before:

![image](https://github.com/user-attachments/assets/899d25a9-80e9-48d5-a820-79c911c858e9)
After:

![image](https://github.com/user-attachments/assets/cf2a7407-909a-41db-9957-19d9214af57e)

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-10 01:41:52 +00:00
Giteabot
ed1828ca92
Fix ACME panic (#33178) (#33186)
Backport #33178 by @wxiaoguang

Fix #33177, Manually tested:

````
1.7364311850484018e+09	info	maintenance	started background certificate maintenance	{"cache": "0x1400ca64180"}
1.736431185054049e+09	info	obtain	acquiring lock	{"identifier": "example.com"}
1.736431185058073e+09	info	obtain	lock acquired	{"identifier": "example.com"}
1.736431185058133e+09	info	obtain	obtaining certificate	{"identifier": "example.com"}
````

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-10 09:06:40 +08:00
Giteabot
3cfff5af0d
Move repo size to sidebar (#33155) (#33182)
Backport #33155 by @yp05327


![image](https://github.com/user-attachments/assets/8b14dbb7-ec36-4596-a6aa-72c14d93309d)

Co-authored-by: yp05327 <576951401@qq.com>
2025-01-09 20:29:37 +00:00
Giteabot
6f6c66a07d
Fix assignee list overlapping in Issue sidebar (#33176) (#33181)
Backport #33176 by wxiaoguang

Fix  #33170

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-09 18:36:32 +00:00
Giteabot
d65af69c2b
Fix pam auth test regression (#33169) (#33174)
Backport #33169 by TheFox0x7

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

Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
2025-01-09 13:33:50 +00:00
Giteabot
12c24c2189
Fix fuzz test (#33156) (#33158)
Backport #33156 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-01-09 07:40:35 +00:00
Lunny Xiao
a330f42f01
Update changelog for v1.23.0 (#33130) 2025-01-09 09:24:34 +08:00
wxiaoguang
531f36ea4a
Fix git remote error check, fix dependencies, fix js error (#33129) (#33133)
And update some dependencies to fix bugs.

Backport  #33129, #33136

Fix #32889
Fix #33141
Fix #33139

---------

Co-authored-by: yp05327 <576951401@qq.com>
2025-01-08 05:08:44 +00:00
Giteabot
b4f0eed969
Filter reviews of one pull request in memory instead of database to reduce slow response because of lacking database index (#33106) (#33128)
Backport #33106 by @lunny

This PR fixes a performance problem when reviewing a pull request in a
big instance which have many records in the `review` table.
Traditionally, we should add more indexes in that table. But since
dismissed reviews of 1 pull request will not be too many as expected in
a common repository. Filtering reviews in the memory should be more
quick .

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-08 10:43:46 +08:00
Giteabot
63b3a33bf2
fix empty repo updated time (#33120) (#33124)
Backport #33120 by changchaishi

fixes #33119 


Co-authored-by: Chai-Shi <changchaishi@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-06 23:55:04 +00:00
Lunny Xiao
9899989ece
Add missing transaction when set merge (#33113)
backport from #33079 

`SetMerged` should be in a database transaction otherwise it's possible
to have dirty data.
2025-01-06 18:21:14 +00:00
wxiaoguang
0fad40dd8c
Fix package error handling and npm meta and empty repo guide (#33112) 2025-01-06 14:17:28 +08:00
wxiaoguang
e637008fe3
Fix empty git repo handling logic and fix mobile view (#33101) (#33102)
Backport #33101 and UI fix from main (including #33108)
2025-01-05 23:18:02 +08:00
wxiaoguang
fd281518ae
Fix line-number and scroll bugs (#33094) (#33095)
Partially backport #33094 

Fix the scroll bug in issue/pr view page.

Fix a JS error when line number exceeds the max
2025-01-03 23:50:39 +02:00
wxiaoguang
e10d222434
Fix bleve fuzziness search (#33078) (#33087) 2025-01-02 21:45:14 +00:00
wxiaoguang
7a35f90b29
Fix broken forms (#33082) 2025-01-03 03:57:34 +08:00
Giteabot
d371aa3031
Try to fix ACME directory problem (#33072) (#33077)
Backport #33072 by wxiaoguang

Haven't really confirmed, but I think it might fix #32191

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-01-02 05:02:46 +00:00
wxiaoguang
81768675d4
Inherit submodules from template repository content (#16237) (#33068)
Backport #16237 (it more likely a bug fix)

Co-authored-by: Steffen Schröter <steffen@vexar.de>
2025-01-02 12:17:05 +08:00
Giteabot
39cc72562b
feat(action): issue change title notifications (#33050) (#33065)
Backport #33050 by appleboy

action file as below:

```yaml
name: Semantic Pull Request

on:
  pull_request_target:
    types: [edited]
```

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-31 12:33:08 +00:00
Giteabot
bc83fb26ef
Use project's redirect url instead of composing url (#33058) (#33064)
Backport #33058 by lunny

Fix #32992

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-31 20:06:35 +08:00
wxiaoguang
68736ec292
Refactor maven package registry (#33049) (#33057)
Backport #33049
2024-12-31 15:22:09 +08:00
Giteabot
3df11c07a8
Make issue suggestion work for new PR page (#33035) (#33056)
Backport #33035 by wxiaoguang

Fix #33026

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-31 03:56:34 +00:00
Giteabot
96fff862dc
Fix duplicate co-author in squashed merge commit messages (#33020) (#33054) 2024-12-31 03:04:47 +00:00
Giteabot
968c04c7da
Fix issue comment number (#30556) (#33055)
Backport #30556 by @lunny

Fix #22419

Only comments with types `CommentTypeComment` and `CommentTypeReview`
will be counted as conversations of the pull request.
`CommentTypeReview` was missed in the previous implementation.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-31 07:54:40 +08:00
Giteabot
27de60381d
Fix settings not being loaded at CLI (#26402) (#33048)
Backport #26402 by cassiozareck

Closes #25898

Signed-off-by: cassiozareck <cassiomilczareck@gmail.com>
Co-authored-by: cassio zareck <121526696+cassiozareck@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-30 07:39:59 +00:00
Giteabot
d2d763318c
Remove aws go sdk package dependency (#33029) (#33047)
Backport #33029 by lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-30 06:55:06 +00:00
Giteabot
610b2fb88d
Use gitrepo.GetTreePathLatestCommit to get file lastest commit instead from latest commit cache (#32987) (#33046) 2024-12-30 05:17:07 +00:00
Giteabot
0858a36016
use -s -w ldflags for release artifacts (#33041) (#33042)
Backport #33041 by @techknowlogick

fix #33030

Co-authored-by: techknowlogick <techknowlogick@gitea.com>
2024-12-30 04:17:17 +00:00
Giteabot
fef364e7d6
Fix bug automerge cannot be chosed when there is only 1 merge style (#33040) (#33043)
Backport #33040 by @lunny

This is a quick bug fix. Even if there is only 1 merge style, the
dropdown menu will still be displayed to allow users to choose
automerge.

Fix #32448

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-30 11:42:08 +08:00
wxiaoguang
ce6a60a38b
Refactor testfixtures (#33028)
Partial backport of #33024
2024-12-30 02:49:49 +00:00
Giteabot
74159a8855
Fix templating in pull request comparison (#33025) (#33038)
Backport #33025 by TheFox0x7

Adds test for expected behavior

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

---

Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
2024-12-29 18:24:22 +00:00
wxiaoguang
ce6464123f
fix toggle commit body button ui when latest commit message is long (#32997) (#33034)
backport #32997 and #33002
2024-12-29 17:54:23 +00:00
Giteabot
7f0050cf39
Fix review code comment avatar alignment (#33031) (#33032)
Backport #33031 by henrygoodman

Fixes #33017

Co-authored-by: Henry Goodman <79623665+henrygoodman@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-30 00:58:40 +08:00
Giteabot
c102e344f3
Fix bug on activities (#33008) (#33016)
Backport #33008 by @lunny

A repository with no issue will display a random number on activities
page. This is caused by wrong usage of `And` and `Or`.


![9cdbbf81d50aa5d9bd16604e0dab5eb0](https://github.com/user-attachments/assets/828cebdc-bd35-4716-a58c-c1b43ddf8bf0)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-28 05:04:07 +00:00
Giteabot
f27128bf94
fix scoped label ui when contains emoji (#33007) (#33014)
Backport #33007 by metiftikci

Co-authored-by: metiftikci <metiftikci@hotmail.com>
2024-12-28 04:34:53 +00:00
Giteabot
f35ab5cd52
Fix Agit pull request permission check (#32999) (#33005)
Backport #32999 by @a1012112796

user with read permission should also can create agit flow pull request.
looks this logic was broken in
https://github.com/go-gitea/gitea/pull/31033 this pull request try fix
it and add test code.

Signed-off-by: a1012112796 <1012112796@qq.com>
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-28 11:34:28 +08:00
Giteabot
0137bc4e5c
Support for email addresses containing uppercase characters when activating user account (#32998) (#33001)
Backport #32998 by Zettat123

Fix #32807

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-27 11:41:41 +00:00
Giteabot
eed0968c37
Support org labels when adding labels by label names (#32988) (#32996)
Backport #32988 by @Zettat123

Fix #32891

Co-authored-by: Zettat123 <zettat123@gmail.com>
2024-12-27 08:35:36 +08:00
Giteabot
a0b65ed17f
Do not render truncated links in markdown (#32980) (#32983)
Backport #32980 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-26 01:12:18 +08:00
Giteabot
ad1b76540e
demilestone should not include milestone (#32923) (#32979)
Backport #32923 by @lunny

Fix #32887

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-25 08:03:18 +00:00
Giteabot
6636b37a9c
Fix Azure blob object Seek (#32974) (#32975)
Backport #32974 by Zettat123

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-25 06:17:27 +00:00
Giteabot
af5e5e8f00
Fix maven pom inheritance (#32943) (#32976)
Backport #32943 by wxiaoguang

Fix  #30568

At the moment, here only `GroupID` (no `Version`) is parsed & used

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-25 05:47:51 +00:00
Giteabot
0e0ebf68d7
fix textarea newline handle (#32966) (#32977)
Backport #32966 by metiftikci

Co-authored-by: metiftikci <metiftikci@hotmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-25 13:10:14 +08:00
Giteabot
90bd08ceef
Use env GITEA_RUNNER_REGISTRATION_TOKEN as global runner token (#32946) (#32964)
Backport #32946 by wxiaoguang

Fix #23703

When Gitea starts, it reads GITEA_RUNNER_REGISTRATION_TOKEN
or GITEA_RUNNER_REGISTRATION_TOKEN_FILE to add registration token.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-24 00:37:11 +08:00
Giteabot
0c581106d2
Fix outdated tmpl code (#32953) (#32961)
Backport #32953 by wxiaoguang

Some PRs were before tmpl ctx refactoring and used outdated code

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-23 07:57:36 +00:00
Giteabot
e18e31d557
Fix commit range paging (#32944) (#32962)
Backport #32944 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-23 15:32:29 +08:00
Giteabot
e1026feddc
Fix repo avatar conflict (#32958) (#32960)
Backport #32958 by wxiaoguang

Continue even if the avatar deleting fails

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-23 03:17:38 +00:00
Giteabot
d670820722
Use Alpine 3.21 for the docker images (#32924) (#32951) 2024-12-22 22:16:34 +00:00
Giteabot
a8f98fd3be
fix trailing comma not matched in the case of alphanumeric issue (#32945) (#32959)
Backport #32945 by @katsusan

Fix #32428.

Patch the regex to match `,`besides `.` `"` `'` `:` and space.

Co-authored-by: katsu <evergonuaa@gmail.com>
2024-12-23 06:08:35 +08:00
Giteabot
c442c682ef
Use primary as button color (#32949) (#32950)
Backport #32949 by wxiaoguang

* Fix #32871
* Fix #32948

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-22 15:28:36 +00:00
Giteabot
57868c2315
Fix locale (#32937) (#32942) 2024-12-21 19:10:42 -05:00
Giteabot
b1c21880c1
Update i18n.go - Language Picker (#32933) (#32935) 2024-12-21 10:24:17 -05:00
wxiaoguang
1e71ad89ce
Deprecated gopid in log (#32932) 2024-12-20 16:20:51 +00:00
Giteabot
c20642fa99
Relax the version checking for Arch packages (#32908) (#32913)
Backport #32908 by ExplodingDragon

It is mentioned in https://man.archlinux.org/man/PKGBUILD.5: 'The
variable is not allowed to contain colons, forward slashes, hyphens, or
whitespace.'

`_` is also an allowed character, and some software in the Arch Linux
AUR uses this naming convention.

Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-19 19:02:58 +08:00
Giteabot
a4291fd553
Add more load functions to make sure the reference object loaded (#32901) (#32912)
Backport #32901 by @lunny

Fix #32897

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-12-19 10:14:04 +01:00
techknowlogick
fa5a064559
bump x/net (#32896) (#32899)
backport #32896 to 1.23
2024-12-18 22:44:55 +00:00
Giteabot
cb42232080
Fix Arch package metadata introduced incorrect field (#32881) (#32882)
Backport #32881 by ExplodingDragon

Incorrect content was introduced while generating the index, which has
now been removed, and the missing fields have been added.

Co-authored-by: Exploding Dragon <explodingfkl@gmail.com>
2024-12-18 12:56:47 +01:00
Lunny Xiao
c8ffe777cf
Add 1.23.0-rc0 changelog (#32863)
Notice: some of pull requests haven't been added to the changelog

- Which have been backported to v1.22
-
https://github.com/go-gitea/gitea/pulls?q=sort%3Aupdated-desc+is%3Apr+milestone%3A1.23.0+is%3Amerged+label%3Abackport%2Fv1.22
- Which have skip-changelog
-
https://github.com/go-gitea/gitea/pulls?q=sort%3Aupdated-desc+is%3Apr+milestone%3A1.23.0+is%3Amerged+-label%3Abackport%2Fv1.22+label%3Askip-changelog
- Build and Testing
-
https://github.com/go-gitea/gitea/pulls?q=sort%3Aupdated-desc+is%3Apr+milestone%3A1.23.0+is%3Amerged+label%3Abackport%2Fv1.22+label%3Atype%2Ftesting
-
https://github.com/go-gitea/gitea/pulls?q=sort%3Aupdated-desc+is%3Apr+milestone%3A1.23.0+is%3Amerged+label%3Abackport%2Fv1.22+label%3Atopic%2Fbuild
- No `type` label marked may be missed, I need to check one by one, but
I believe those are not too many.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-12-17 12:00:55 +08:00
wxiaoguang
e98dd6ee5b
Backport 1.23 (#32868)
Co-authored-by: delvh <dev.lh@web.de>
2024-12-17 11:58:27 +08:00
3268 changed files with 116449 additions and 99448 deletions

View file

@ -22,25 +22,20 @@ groups:
name: FEATURES name: FEATURES
labels: labels:
- type/feature - type/feature
-
name: API
labels:
- modifies/api
- -
name: ENHANCEMENTS name: ENHANCEMENTS
labels: labels:
- type/enhancement - type/enhancement
- - type/refactoring
name: PERFORMANCE - topic/ui
labels:
- performance/memory
- performance/speed
- performance/bigrepo
- performance/cpu
- -
name: BUGFIXES name: BUGFIXES
labels: labels:
- type/bug - type/bug
-
name: API
labels:
- modifies/api
- -
name: TESTING name: TESTING
labels: labels:

View file

@ -1,13 +1,13 @@
{ {
"name": "Gitea DevContainer", "name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm", "image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
"features": { "features": {
// installs nodejs into container // installs nodejs into container
"ghcr.io/devcontainers/features/node:1": { "ghcr.io/devcontainers/features/node:1": {
"version": "lts" "version": "20"
}, },
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {}, "ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
"ghcr.io/devcontainers-extra/features/poetry:2": {}, "ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": { "ghcr.io/devcontainers/features/python:1": {
"version": "3.12" "version": "3.12"
}, },

View file

@ -36,6 +36,15 @@ _testmain.go
coverage.all coverage.all
cpu.out cpu.out
/modules/migration/bindata.go
/modules/migration/bindata.go.hash
/modules/options/bindata.go
/modules/options/bindata.go.hash
/modules/public/bindata.go
/modules/public/bindata.go.hash
/modules/templates/bindata.go
/modules/templates/bindata.go.hash
*.db *.db
*.log *.log
@ -70,6 +79,18 @@ cpu.out
/public/assets/fonts /public/assets/fonts
/public/assets/img/avatar /public/assets/img/avatar
/vendor /vendor
/web_src/fomantic/node_modules
/web_src/fomantic/build/*
!/web_src/fomantic/build/semantic.js
!/web_src/fomantic/build/semantic.css
!/web_src/fomantic/build/themes
/web_src/fomantic/build/themes/*
!/web_src/fomantic/build/themes/default
/web_src/fomantic/build/themes/default/assets/*
!/web_src/fomantic/build/themes/default/assets/fonts
/web_src/fomantic/build/themes/default/assets/fonts/*
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
/VERSION /VERSION
/.air /.air
/.go-licenses /.go-licenses

View file

@ -12,15 +12,11 @@ insert_final_newline = true
[*.{go,tmpl,html}] [*.{go,tmpl,html}]
indent_style = tab indent_style = tab
[go.*]
indent_style = tab
[templates/custom/*.tmpl] [templates/custom/*.tmpl]
insert_final_newline = false insert_final_newline = false
[templates/swagger/v1_json.tmpl] [templates/swagger/v1_json.tmpl]
indent_style = space indent_style = space
insert_final_newline = false
[templates/user/auth/oidc_wellknown.tmpl] [templates/user/auth/oidc_wellknown.tmpl]
indent_style = space indent_style = space

File diff suppressed because it is too large Load diff

967
.eslintrc.yaml Normal file
View file

@ -0,0 +1,967 @@
root: true
reportUnusedDisableDirectives: true
ignorePatterns:
- /web_src/js/vendor
- /web_src/fomantic
- /public/assets/js
parser: "@typescript-eslint/parser"
parserOptions:
sourceType: module
ecmaVersion: latest
project: true
extraFileExtensions: [".vue"]
parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
settings:
import-x/extensions: [".js", ".ts"]
import-x/parsers:
"@typescript-eslint/parser": [".js", ".ts"]
import-x/resolver:
typescript: true
plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- "@stylistic/eslint-plugin-js"
- "@typescript-eslint/eslint-plugin"
- eslint-plugin-array-func
- eslint-plugin-github
- eslint-plugin-import-x
- eslint-plugin-no-jquery
- eslint-plugin-no-use-extend-native
- eslint-plugin-regexp
- eslint-plugin-sonarjs
- eslint-plugin-unicorn
- eslint-plugin-vitest
- eslint-plugin-vitest-globals
- eslint-plugin-wc
env:
es2024: true
node: true
overrides:
- files: ["web_src/**/*"]
globals:
__webpack_public_path__: true
process: false # https://github.com/webpack/webpack/issues/15833
- files: ["web_src/**/*", "docs/**/*"]
env:
browser: true
node: false
- files: ["web_src/**/*worker.*"]
env:
worker: true
rules:
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top]
- files: ["*.config.*"]
rules:
import-x/no-unused-modules: [0]
- files: ["**/*.d.ts"]
rules:
import-x/no-unused-modules: [0]
"@typescript-eslint/consistent-type-definitions": [0]
"@typescript-eslint/consistent-type-imports": [0]
- files: ["web_src/js/types.ts"]
rules:
import-x/no-unused-modules: [0]
- files: ["**/*.test.*", "web_src/js/test/setup.ts"]
env:
vitest-globals/env: true
rules:
vitest/consistent-test-filename: [0]
vitest/consistent-test-it: [0]
vitest/expect-expect: [0]
vitest/max-expects: [0]
vitest/max-nested-describe: [0]
vitest/no-alias-methods: [0]
vitest/no-commented-out-tests: [0]
vitest/no-conditional-expect: [0]
vitest/no-conditional-in-test: [0]
vitest/no-conditional-tests: [0]
vitest/no-disabled-tests: [0]
vitest/no-done-callback: [0]
vitest/no-duplicate-hooks: [0]
vitest/no-focused-tests: [0]
vitest/no-hooks: [0]
vitest/no-identical-title: [2]
vitest/no-interpolation-in-snapshots: [0]
vitest/no-large-snapshots: [0]
vitest/no-mocks-import: [0]
vitest/no-restricted-matchers: [0]
vitest/no-restricted-vi-methods: [0]
vitest/no-standalone-expect: [0]
vitest/no-test-prefixes: [0]
vitest/no-test-return-statement: [0]
vitest/prefer-called-with: [0]
vitest/prefer-comparison-matcher: [0]
vitest/prefer-each: [0]
vitest/prefer-equality-matcher: [0]
vitest/prefer-expect-resolves: [0]
vitest/prefer-hooks-in-order: [0]
vitest/prefer-hooks-on-top: [2]
vitest/prefer-lowercase-title: [0]
vitest/prefer-mock-promise-shorthand: [0]
vitest/prefer-snapshot-hint: [0]
vitest/prefer-spy-on: [0]
vitest/prefer-strict-equal: [0]
vitest/prefer-to-be: [0]
vitest/prefer-to-be-falsy: [0]
vitest/prefer-to-be-object: [0]
vitest/prefer-to-be-truthy: [0]
vitest/prefer-to-contain: [0]
vitest/prefer-to-have-length: [0]
vitest/prefer-todo: [0]
vitest/require-hook: [0]
vitest/require-to-throw-message: [0]
vitest/require-top-level-describe: [0]
vitest/valid-describe-callback: [2]
vitest/valid-expect: [2]
vitest/valid-title: [2]
- files: ["web_src/js/modules/fetch.ts", "web_src/js/standalone/**/*"]
rules:
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
- files: ["**/*.vue"]
plugins:
- eslint-plugin-vue
- eslint-plugin-vue-scoped-css
extends:
- plugin:vue/vue3-recommended
- plugin:vue-scoped-css/vue3-recommended
rules:
vue/attributes-order: [0]
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
vue/max-attributes-per-line: [0]
vue/singleline-html-element-content-newline: [0]
- files: ["tests/e2e/**"]
plugins:
- eslint-plugin-playwright
extends: plugin:playwright/recommended
rules:
"@eslint-community/eslint-comments/disable-enable-pair": [2]
"@eslint-community/eslint-comments/no-aggregating-enable": [2]
"@eslint-community/eslint-comments/no-duplicate-disable": [2]
"@eslint-community/eslint-comments/no-restricted-disable": [0]
"@eslint-community/eslint-comments/no-unlimited-disable": [2]
"@eslint-community/eslint-comments/no-unused-disable": [2]
"@eslint-community/eslint-comments/no-unused-enable": [2]
"@eslint-community/eslint-comments/no-use": [0]
"@eslint-community/eslint-comments/require-description": [0]
"@stylistic/js/array-bracket-newline": [0]
"@stylistic/js/array-bracket-spacing": [2, never]
"@stylistic/js/array-element-newline": [0]
"@stylistic/js/arrow-parens": [2, always]
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}]
"@stylistic/js/block-spacing": [0]
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}]
"@stylistic/js/comma-dangle": [2, always-multiline]
"@stylistic/js/comma-spacing": [2, {before: false, after: true}]
"@stylistic/js/comma-style": [2, last]
"@stylistic/js/computed-property-spacing": [2, never]
"@stylistic/js/dot-location": [2, property]
"@stylistic/js/eol-last": [2]
"@stylistic/js/function-call-argument-newline": [0]
"@stylistic/js/function-call-spacing": [2, never]
"@stylistic/js/function-paren-newline": [0]
"@stylistic/js/generator-star-spacing": [0]
"@stylistic/js/implicit-arrow-linebreak": [0]
"@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}]
"@stylistic/js/key-spacing": [2]
"@stylistic/js/keyword-spacing": [2]
"@stylistic/js/line-comment-position": [0]
"@stylistic/js/linebreak-style": [2, unix]
"@stylistic/js/lines-around-comment": [0]
"@stylistic/js/lines-between-class-members": [0]
"@stylistic/js/max-len": [0]
"@stylistic/js/max-statements-per-line": [0]
"@stylistic/js/multiline-comment-style": [0]
"@stylistic/js/multiline-ternary": [0]
"@stylistic/js/new-parens": [2]
"@stylistic/js/newline-per-chained-call": [0]
"@stylistic/js/no-confusing-arrow": [0]
"@stylistic/js/no-extra-parens": [0]
"@stylistic/js/no-extra-semi": [2]
"@stylistic/js/no-floating-decimal": [0]
"@stylistic/js/no-mixed-operators": [0]
"@stylistic/js/no-mixed-spaces-and-tabs": [2]
"@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
"@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}]
"@stylistic/js/no-tabs": [2]
"@stylistic/js/no-trailing-spaces": [2]
"@stylistic/js/no-whitespace-before-property": [2]
"@stylistic/js/nonblock-statement-body-position": [2]
"@stylistic/js/object-curly-newline": [0]
"@stylistic/js/object-curly-spacing": [2, never]
"@stylistic/js/object-property-newline": [0]
"@stylistic/js/one-var-declaration-per-line": [0]
"@stylistic/js/operator-linebreak": [2, after]
"@stylistic/js/padded-blocks": [2, never]
"@stylistic/js/padding-line-between-statements": [0]
"@stylistic/js/quote-props": [0]
"@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
"@stylistic/js/rest-spread-spacing": [2, never]
"@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}]
"@stylistic/js/semi-spacing": [2, {before: false, after: true}]
"@stylistic/js/semi-style": [2, last]
"@stylistic/js/space-before-blocks": [2, always]
"@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}]
"@stylistic/js/space-in-parens": [2, never]
"@stylistic/js/space-infix-ops": [2]
"@stylistic/js/space-unary-ops": [2]
"@stylistic/js/spaced-comment": [2, always]
"@stylistic/js/switch-colon-spacing": [2]
"@stylistic/js/template-curly-spacing": [2, never]
"@stylistic/js/template-tag-spacing": [2, never]
"@stylistic/js/wrap-iife": [2, inside]
"@stylistic/js/wrap-regex": [0]
"@stylistic/js/yield-star-spacing": [2, after]
"@typescript-eslint/adjacent-overload-signatures": [0]
"@typescript-eslint/array-type": [0]
"@typescript-eslint/await-thenable": [2]
"@typescript-eslint/ban-ts-comment": [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}]
"@typescript-eslint/ban-tslint-comment": [0]
"@typescript-eslint/class-literal-property-style": [0]
"@typescript-eslint/class-methods-use-this": [0]
"@typescript-eslint/consistent-generic-constructors": [0]
"@typescript-eslint/consistent-indexed-object-style": [0]
"@typescript-eslint/consistent-return": [0]
"@typescript-eslint/consistent-type-assertions": [2, {assertionStyle: as, objectLiteralTypeAssertions: allow}]
"@typescript-eslint/consistent-type-definitions": [2, type]
"@typescript-eslint/consistent-type-exports": [2, {fixMixedExportsWithInlineTypeSpecifier: false}]
"@typescript-eslint/consistent-type-imports": [2, {prefer: type-imports, fixStyle: separate-type-imports, disallowTypeAnnotations: true}]
"@typescript-eslint/default-param-last": [0]
"@typescript-eslint/dot-notation": [0]
"@typescript-eslint/explicit-function-return-type": [0]
"@typescript-eslint/explicit-member-accessibility": [0]
"@typescript-eslint/explicit-module-boundary-types": [0]
"@typescript-eslint/init-declarations": [0]
"@typescript-eslint/max-params": [0]
"@typescript-eslint/member-ordering": [0]
"@typescript-eslint/method-signature-style": [0]
"@typescript-eslint/naming-convention": [0]
"@typescript-eslint/no-array-constructor": [2]
"@typescript-eslint/no-array-delete": [2]
"@typescript-eslint/no-base-to-string": [0]
"@typescript-eslint/no-confusing-non-null-assertion": [2]
"@typescript-eslint/no-confusing-void-expression": [0]
"@typescript-eslint/no-deprecated": [2]
"@typescript-eslint/no-dupe-class-members": [0]
"@typescript-eslint/no-duplicate-enum-values": [2]
"@typescript-eslint/no-duplicate-type-constituents": [2, {ignoreUnions: true}]
"@typescript-eslint/no-dynamic-delete": [0]
"@typescript-eslint/no-empty-function": [0]
"@typescript-eslint/no-empty-interface": [0]
"@typescript-eslint/no-empty-object-type": [2]
"@typescript-eslint/no-explicit-any": [0]
"@typescript-eslint/no-extra-non-null-assertion": [2]
"@typescript-eslint/no-extraneous-class": [0]
"@typescript-eslint/no-floating-promises": [0]
"@typescript-eslint/no-for-in-array": [2]
"@typescript-eslint/no-implied-eval": [2]
"@typescript-eslint/no-import-type-side-effects": [0] # dupe with consistent-type-imports
"@typescript-eslint/no-inferrable-types": [0]
"@typescript-eslint/no-invalid-this": [0]
"@typescript-eslint/no-invalid-void-type": [0]
"@typescript-eslint/no-loop-func": [0]
"@typescript-eslint/no-loss-of-precision": [0]
"@typescript-eslint/no-magic-numbers": [0]
"@typescript-eslint/no-meaningless-void-operator": [0]
"@typescript-eslint/no-misused-new": [2]
"@typescript-eslint/no-misused-promises": [2, {checksVoidReturn: {attributes: false, arguments: false}}]
"@typescript-eslint/no-mixed-enums": [0]
"@typescript-eslint/no-namespace": [2]
"@typescript-eslint/no-non-null-asserted-nullish-coalescing": [0]
"@typescript-eslint/no-non-null-asserted-optional-chain": [2]
"@typescript-eslint/no-non-null-assertion": [0]
"@typescript-eslint/no-redeclare": [0]
"@typescript-eslint/no-redundant-type-constituents": [2]
"@typescript-eslint/no-require-imports": [2]
"@typescript-eslint/no-restricted-imports": [0]
"@typescript-eslint/no-restricted-types": [0]
"@typescript-eslint/no-shadow": [0]
"@typescript-eslint/no-this-alias": [0] # handled by unicorn/no-this-assignment
"@typescript-eslint/no-unnecessary-boolean-literal-compare": [0]
"@typescript-eslint/no-unnecessary-condition": [0]
"@typescript-eslint/no-unnecessary-qualifier": [0]
"@typescript-eslint/no-unnecessary-template-expression": [0]
"@typescript-eslint/no-unnecessary-type-arguments": [0]
"@typescript-eslint/no-unnecessary-type-assertion": [2]
"@typescript-eslint/no-unnecessary-type-constraint": [2]
"@typescript-eslint/no-unsafe-argument": [0]
"@typescript-eslint/no-unsafe-assignment": [0]
"@typescript-eslint/no-unsafe-call": [0]
"@typescript-eslint/no-unsafe-declaration-merging": [2]
"@typescript-eslint/no-unsafe-enum-comparison": [2]
"@typescript-eslint/no-unsafe-function-type": [2]
"@typescript-eslint/no-unsafe-member-access": [0]
"@typescript-eslint/no-unsafe-return": [0]
"@typescript-eslint/no-unsafe-unary-minus": [2]
"@typescript-eslint/no-unused-expressions": [0]
"@typescript-eslint/no-unused-vars": [2, {vars: all, args: all, caughtErrors: all, ignoreRestSiblings: false, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_}]
"@typescript-eslint/no-use-before-define": [0]
"@typescript-eslint/no-useless-constructor": [0]
"@typescript-eslint/no-useless-empty-export": [0]
"@typescript-eslint/no-wrapper-object-types": [2]
"@typescript-eslint/non-nullable-type-assertion-style": [0]
"@typescript-eslint/only-throw-error": [2]
"@typescript-eslint/parameter-properties": [0]
"@typescript-eslint/prefer-as-const": [2]
"@typescript-eslint/prefer-destructuring": [0]
"@typescript-eslint/prefer-enum-initializers": [0]
"@typescript-eslint/prefer-find": [2]
"@typescript-eslint/prefer-for-of": [2]
"@typescript-eslint/prefer-function-type": [2]
"@typescript-eslint/prefer-includes": [2]
"@typescript-eslint/prefer-literal-enum-member": [0]
"@typescript-eslint/prefer-namespace-keyword": [0]
"@typescript-eslint/prefer-nullish-coalescing": [0]
"@typescript-eslint/prefer-optional-chain": [2, {requireNullish: true}]
"@typescript-eslint/prefer-promise-reject-errors": [0]
"@typescript-eslint/prefer-readonly": [0]
"@typescript-eslint/prefer-readonly-parameter-types": [0]
"@typescript-eslint/prefer-reduce-type-parameter": [0]
"@typescript-eslint/prefer-regexp-exec": [0]
"@typescript-eslint/prefer-return-this-type": [0]
"@typescript-eslint/prefer-string-starts-ends-with": [2, {allowSingleElementEquality: always}]
"@typescript-eslint/promise-function-async": [0]
"@typescript-eslint/require-array-sort-compare": [0]
"@typescript-eslint/require-await": [0]
"@typescript-eslint/restrict-plus-operands": [2]
"@typescript-eslint/restrict-template-expressions": [0]
"@typescript-eslint/return-await": [0]
"@typescript-eslint/strict-boolean-expressions": [0]
"@typescript-eslint/switch-exhaustiveness-check": [0]
"@typescript-eslint/triple-slash-reference": [2]
"@typescript-eslint/typedef": [0]
"@typescript-eslint/unbound-method": [0] # too many false-positives
"@typescript-eslint/unified-signatures": [2]
accessor-pairs: [2]
array-callback-return: [2, {checkForEach: true}]
array-func/avoid-reverse: [2]
array-func/from-map: [2]
array-func/no-unnecessary-this-arg: [2]
array-func/prefer-array-from: [2]
array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map
array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat
arrow-body-style: [0]
block-scoped-var: [2]
camelcase: [0]
capitalized-comments: [0]
class-methods-use-this: [0]
complexity: [0]
consistent-return: [0]
consistent-this: [0]
constructor-super: [2]
curly: [0]
default-case-last: [2]
default-case: [0]
default-param-last: [0]
dot-notation: [0]
eqeqeq: [2]
for-direction: [2]
func-name-matching: [2]
func-names: [0]
func-style: [0]
getter-return: [2]
github/a11y-aria-label-is-well-formatted: [0]
github/a11y-no-title-attribute: [0]
github/a11y-no-visually-hidden-interactive-element: [0]
github/a11y-role-supports-aria-props: [0]
github/a11y-svg-has-accessible-name: [0]
github/array-foreach: [0]
github/async-currenttarget: [2]
github/async-preventdefault: [2]
github/authenticity-token: [0]
github/get-attribute: [0]
github/js-class-name: [0]
github/no-blur: [0]
github/no-d-none: [0]
github/no-dataset: [2]
github/no-dynamic-script-tag: [2]
github/no-implicit-buggy-globals: [2]
github/no-inner-html: [0]
github/no-innerText: [2]
github/no-then: [2]
github/no-useless-passive: [2]
github/prefer-observers: [2]
github/require-passive-events: [2]
github/unescaped-html-literal: [0]
grouped-accessor-pairs: [2]
guard-for-in: [0]
id-blacklist: [0]
id-length: [0]
id-match: [0]
import-x/consistent-type-specifier-style: [0]
import-x/default: [0]
import-x/dynamic-import-chunkname: [0]
import-x/export: [2]
import-x/exports-last: [0]
import-x/extensions: [2, always, {ignorePackages: true}]
import-x/first: [2]
import-x/group-exports: [0]
import-x/max-dependencies: [0]
import-x/named: [2]
import-x/namespace: [0]
import-x/newline-after-import: [0]
import-x/no-absolute-path: [0]
import-x/no-amd: [2]
import-x/no-anonymous-default-export: [0]
import-x/no-commonjs: [2]
import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}]
import-x/no-default-export: [0]
import-x/no-deprecated: [0]
import-x/no-dynamic-require: [0]
import-x/no-empty-named-blocks: [2]
import-x/no-extraneous-dependencies: [2]
import-x/no-import-module-exports: [0]
import-x/no-internal-modules: [0]
import-x/no-mutable-exports: [0]
import-x/no-named-as-default-member: [0]
import-x/no-named-as-default: [0]
import-x/no-named-default: [0]
import-x/no-named-export: [0]
import-x/no-namespace: [0]
import-x/no-nodejs-modules: [0]
import-x/no-relative-packages: [0]
import-x/no-relative-parent-imports: [0]
import-x/no-restricted-paths: [0]
import-x/no-self-import: [2]
import-x/no-unassigned-import: [0]
import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
import-x/no-unused-modules: [2, {unusedExports: true}]
import-x/no-useless-path-segments: [2, {commonjs: true}]
import-x/no-webpack-loader-syntax: [2]
import-x/order: [0]
import-x/prefer-default-export: [0]
import-x/unambiguous: [0]
init-declarations: [0]
line-comment-position: [0]
logical-assignment-operators: [0]
max-classes-per-file: [0]
max-depth: [0]
max-lines-per-function: [0]
max-lines: [0]
max-nested-callbacks: [0]
max-params: [0]
max-statements: [0]
multiline-comment-style: [2, separate-lines]
new-cap: [0]
no-alert: [0]
no-array-constructor: [0] # handled by @typescript-eslint/no-array-constructor
no-async-promise-executor: [0]
no-await-in-loop: [0]
no-bitwise: [0]
no-buffer-constructor: [0]
no-caller: [2]
no-case-declarations: [2]
no-class-assign: [2]
no-compare-neg-zero: [2]
no-cond-assign: [2, except-parens]
no-console: [1, {allow: [debug, info, warn, error]}]
no-const-assign: [2]
no-constant-binary-expression: [2]
no-constant-condition: [0]
no-constructor-return: [2]
no-continue: [0]
no-control-regex: [0]
no-debugger: [1]
no-delete-var: [2]
no-div-regex: [0]
no-dupe-args: [2]
no-dupe-class-members: [2]
no-dupe-else-if: [2]
no-dupe-keys: [2]
no-duplicate-case: [2]
no-duplicate-imports: [0]
no-else-return: [2]
no-empty-character-class: [2]
no-empty-function: [0]
no-empty-pattern: [2]
no-empty-static-block: [2]
no-empty: [2, {allowEmptyCatch: true}]
no-eq-null: [2]
no-eval: [2]
no-ex-assign: [2]
no-extend-native: [2]
no-extra-bind: [2]
no-extra-boolean-cast: [2]
no-extra-label: [0]
no-fallthrough: [2]
no-func-assign: [2]
no-global-assign: [2]
no-implicit-coercion: [2]
no-implicit-globals: [0]
no-implied-eval: [0] # handled by @typescript-eslint/no-implied-eval
no-import-assign: [2]
no-inline-comments: [0]
no-inner-declarations: [2]
no-invalid-regexp: [2]
no-invalid-this: [0]
no-irregular-whitespace: [2]
no-iterator: [2]
no-jquery/no-ajax-events: [2]
no-jquery/no-ajax: [2]
no-jquery/no-and-self: [2]
no-jquery/no-animate-toggle: [2]
no-jquery/no-animate: [2]
no-jquery/no-append-html: [2]
no-jquery/no-attr: [2]
no-jquery/no-bind: [2]
no-jquery/no-box-model: [2]
no-jquery/no-browser: [2]
no-jquery/no-camel-case: [2]
no-jquery/no-class-state: [2]
no-jquery/no-class: [0]
no-jquery/no-clone: [2]
no-jquery/no-closest: [0]
no-jquery/no-constructor-attributes: [2]
no-jquery/no-contains: [2]
no-jquery/no-context-prop: [2]
no-jquery/no-css: [2]
no-jquery/no-data: [0]
no-jquery/no-deferred: [2]
no-jquery/no-delegate: [2]
no-jquery/no-done-fail: [2]
no-jquery/no-each-collection: [0]
no-jquery/no-each-util: [0]
no-jquery/no-each: [0]
no-jquery/no-error-shorthand: [2]
no-jquery/no-error: [2]
no-jquery/no-escape-selector: [2]
no-jquery/no-event-shorthand: [2]
no-jquery/no-extend: [2]
no-jquery/no-fade: [2]
no-jquery/no-filter: [0]
no-jquery/no-find-collection: [0]
no-jquery/no-find-util: [2]
no-jquery/no-find: [0]
no-jquery/no-fx-interval: [2]
no-jquery/no-fx: [2]
no-jquery/no-global-eval: [2]
no-jquery/no-global-selector: [0]
no-jquery/no-grep: [2]
no-jquery/no-has: [2]
no-jquery/no-hold-ready: [2]
no-jquery/no-html: [0]
no-jquery/no-in-array: [2]
no-jquery/no-is-array: [2]
no-jquery/no-is-empty-object: [2]
no-jquery/no-is-function: [2]
no-jquery/no-is-numeric: [2]
no-jquery/no-is-plain-object: [2]
no-jquery/no-is-window: [2]
no-jquery/no-is: [2]
no-jquery/no-jquery-constructor: [0]
no-jquery/no-live: [2]
no-jquery/no-load-shorthand: [2]
no-jquery/no-load: [2]
no-jquery/no-map-collection: [0]
no-jquery/no-map-util: [2]
no-jquery/no-map: [2]
no-jquery/no-merge: [2]
no-jquery/no-node-name: [2]
no-jquery/no-noop: [2]
no-jquery/no-now: [2]
no-jquery/no-on-ready: [2]
no-jquery/no-other-methods: [0]
no-jquery/no-other-utils: [2]
no-jquery/no-param: [2]
no-jquery/no-parent: [0]
no-jquery/no-parents: [2]
no-jquery/no-parse-html-literal: [2]
no-jquery/no-parse-html: [2]
no-jquery/no-parse-json: [2]
no-jquery/no-parse-xml: [2]
no-jquery/no-prop: [2]
no-jquery/no-proxy: [2]
no-jquery/no-ready-shorthand: [2]
no-jquery/no-ready: [2]
no-jquery/no-selector-prop: [2]
no-jquery/no-serialize: [2]
no-jquery/no-size: [2]
no-jquery/no-sizzle: [2]
no-jquery/no-slide: [2]
no-jquery/no-sub: [2]
no-jquery/no-support: [2]
no-jquery/no-text: [2]
no-jquery/no-trigger: [0]
no-jquery/no-trim: [2]
no-jquery/no-type: [2]
no-jquery/no-unique: [2]
no-jquery/no-unload-shorthand: [2]
no-jquery/no-val: [0]
no-jquery/no-visibility: [2]
no-jquery/no-when: [2]
no-jquery/no-wrap: [2]
no-jquery/variable-pattern: [2]
no-label-var: [2]
no-labels: [0] # handled by no-restricted-syntax
no-lone-blocks: [2]
no-lonely-if: [0]
no-loop-func: [0]
no-loss-of-precision: [2]
no-magic-numbers: [0]
no-misleading-character-class: [2]
no-multi-assign: [0]
no-multi-str: [2]
no-negated-condition: [0]
no-nested-ternary: [0]
no-new-func: [2]
no-new-native-nonconstructor: [2]
no-new-object: [2]
no-new-symbol: [2]
no-new-wrappers: [2]
no-new: [0]
no-nonoctal-decimal-escape: [2]
no-obj-calls: [2]
no-octal-escape: [2]
no-octal: [2]
no-param-reassign: [0]
no-plusplus: [0]
no-promise-executor-return: [0]
no-proto: [2]
no-prototype-builtins: [2]
no-redeclare: [0] # must be disabled for typescript overloads
no-regex-spaces: [2]
no-restricted-exports: [0]
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename]
no-restricted-imports: [0]
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.ts instead"}]
no-return-assign: [0]
no-script-url: [2]
no-self-assign: [2, {props: true}]
no-self-compare: [2]
no-sequences: [2]
no-setter-return: [2]
no-shadow-restricted-names: [2]
no-shadow: [0]
no-sparse-arrays: [2]
no-template-curly-in-string: [2]
no-ternary: [0]
no-this-before-super: [2]
no-throw-literal: [2]
no-undef-init: [2]
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
no-undefined: [0]
no-underscore-dangle: [0]
no-unexpected-multiline: [2]
no-unmodified-loop-condition: [2]
no-unneeded-ternary: [2]
no-unreachable-loop: [2]
no-unreachable: [2]
no-unsafe-finally: [2]
no-unsafe-negation: [2]
no-unused-expressions: [2]
no-unused-labels: [2]
no-unused-private-class-members: [2]
no-unused-vars: [0] # handled by @typescript-eslint/no-unused-vars
no-use-before-define: [2, {functions: false, classes: true, variables: true, allowNamedExports: true}]
no-use-extend-native/no-use-extend-native: [2]
no-useless-backreference: [2]
no-useless-call: [2]
no-useless-catch: [2]
no-useless-computed-key: [2]
no-useless-concat: [2]
no-useless-constructor: [2]
no-useless-escape: [2]
no-useless-rename: [2]
no-useless-return: [2]
no-var: [2]
no-void: [2]
no-warning-comments: [0]
no-with: [0] # handled by no-restricted-syntax
object-shorthand: [2, always]
one-var-declaration-per-line: [0]
one-var: [0]
operator-assignment: [2, always]
operator-linebreak: [2, after]
prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}]
prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}]
prefer-destructuring: [0]
prefer-exponentiation-operator: [2]
prefer-named-capture-group: [0]
prefer-numeric-literals: [2]
prefer-object-has-own: [2]
prefer-object-spread: [2]
prefer-promise-reject-errors: [2, {allowEmptyReject: false}]
prefer-regex-literals: [2]
prefer-rest-params: [2]
prefer-spread: [2]
prefer-template: [2]
radix: [2, as-needed]
regexp/confusing-quantifier: [2]
regexp/control-character-escape: [2]
regexp/hexadecimal-escape: [0]
regexp/letter-case: [0]
regexp/match-any: [2]
regexp/negation: [2]
regexp/no-contradiction-with-assertion: [0]
regexp/no-control-character: [0]
regexp/no-dupe-characters-character-class: [2]
regexp/no-dupe-disjunctions: [2]
regexp/no-empty-alternative: [2]
regexp/no-empty-capturing-group: [2]
regexp/no-empty-character-class: [0]
regexp/no-empty-group: [2]
regexp/no-empty-lookarounds-assertion: [2]
regexp/no-empty-string-literal: [2]
regexp/no-escape-backspace: [2]
regexp/no-extra-lookaround-assertions: [0]
regexp/no-invalid-regexp: [2]
regexp/no-invisible-character: [2]
regexp/no-lazy-ends: [2]
regexp/no-legacy-features: [2]
regexp/no-misleading-capturing-group: [0]
regexp/no-misleading-unicode-character: [0]
regexp/no-missing-g-flag: [2]
regexp/no-non-standard-flag: [2]
regexp/no-obscure-range: [2]
regexp/no-octal: [2]
regexp/no-optional-assertion: [2]
regexp/no-potentially-useless-backreference: [2]
regexp/no-standalone-backslash: [2]
regexp/no-super-linear-backtracking: [0]
regexp/no-super-linear-move: [0]
regexp/no-trivially-nested-assertion: [2]
regexp/no-trivially-nested-quantifier: [2]
regexp/no-unused-capturing-group: [0]
regexp/no-useless-assertions: [2]
regexp/no-useless-backreference: [2]
regexp/no-useless-character-class: [2]
regexp/no-useless-dollar-replacements: [2]
regexp/no-useless-escape: [2]
regexp/no-useless-flag: [2]
regexp/no-useless-lazy: [2]
regexp/no-useless-non-capturing-group: [2]
regexp/no-useless-quantifier: [2]
regexp/no-useless-range: [2]
regexp/no-useless-set-operand: [2]
regexp/no-useless-string-literal: [2]
regexp/no-useless-two-nums-quantifier: [2]
regexp/no-zero-quantifier: [2]
regexp/optimal-lookaround-quantifier: [2]
regexp/optimal-quantifier-concatenation: [0]
regexp/prefer-character-class: [0]
regexp/prefer-d: [0]
regexp/prefer-escape-replacement-dollar-char: [0]
regexp/prefer-lookaround: [0]
regexp/prefer-named-backreference: [0]
regexp/prefer-named-capture-group: [0]
regexp/prefer-named-replacement: [0]
regexp/prefer-plus-quantifier: [2]
regexp/prefer-predefined-assertion: [2]
regexp/prefer-quantifier: [0]
regexp/prefer-question-quantifier: [2]
regexp/prefer-range: [2]
regexp/prefer-regexp-exec: [2]
regexp/prefer-regexp-test: [2]
regexp/prefer-result-array-groups: [0]
regexp/prefer-set-operation: [2]
regexp/prefer-star-quantifier: [2]
regexp/prefer-unicode-codepoint-escapes: [2]
regexp/prefer-w: [0]
regexp/require-unicode-regexp: [0]
regexp/simplify-set-operations: [2]
regexp/sort-alternatives: [0]
regexp/sort-character-class-elements: [0]
regexp/sort-flags: [0]
regexp/strict: [2]
regexp/unicode-escape: [0]
regexp/use-ignore-case: [0]
require-atomic-updates: [0]
require-await: [0] # handled by @typescript-eslint/require-await
require-unicode-regexp: [0]
require-yield: [2]
sonarjs/cognitive-complexity: [0]
sonarjs/elseif-without-else: [0]
sonarjs/max-switch-cases: [0]
sonarjs/no-all-duplicated-branches: [2]
sonarjs/no-collapsible-if: [0]
sonarjs/no-collection-size-mischeck: [2]
sonarjs/no-duplicate-string: [0]
sonarjs/no-duplicated-branches: [0]
sonarjs/no-element-overwrite: [2]
sonarjs/no-empty-collection: [2]
sonarjs/no-extra-arguments: [2]
sonarjs/no-gratuitous-expressions: [2]
sonarjs/no-identical-conditions: [2]
sonarjs/no-identical-expressions: [2]
sonarjs/no-identical-functions: [2, 5]
sonarjs/no-ignored-return: [2]
sonarjs/no-inverted-boolean-check: [2]
sonarjs/no-nested-switch: [0]
sonarjs/no-nested-template-literals: [0]
sonarjs/no-one-iteration-loop: [2]
sonarjs/no-redundant-boolean: [2]
sonarjs/no-redundant-jump: [2]
sonarjs/no-same-line-conditional: [2]
sonarjs/no-small-switch: [0]
sonarjs/no-unused-collection: [2]
sonarjs/no-use-of-empty-return-value: [2]
sonarjs/no-useless-catch: [2]
sonarjs/non-existent-operator: [2]
sonarjs/prefer-immediate-return: [0]
sonarjs/prefer-object-literal: [0]
sonarjs/prefer-single-boolean-return: [0]
sonarjs/prefer-while: [2]
sort-imports: [0]
sort-keys: [0]
sort-vars: [0]
strict: [0]
symbol-description: [2]
unicode-bom: [2, never]
unicorn/better-regex: [0]
unicorn/catch-error-name: [0]
unicorn/consistent-destructuring: [2]
unicorn/consistent-empty-array-spread: [2]
unicorn/consistent-existence-index-check: [0]
unicorn/consistent-function-scoping: [0]
unicorn/custom-error-definition: [0]
unicorn/empty-brace-spaces: [2]
unicorn/error-message: [0]
unicorn/escape-case: [0]
unicorn/expiring-todo-comments: [0]
unicorn/explicit-length-check: [0]
unicorn/filename-case: [0]
unicorn/import-index: [0]
unicorn/import-style: [0]
unicorn/new-for-builtins: [2]
unicorn/no-abusive-eslint-disable: [0]
unicorn/no-anonymous-default-export: [0]
unicorn/no-array-callback-reference: [0]
unicorn/no-array-for-each: [2]
unicorn/no-array-method-this-argument: [2]
unicorn/no-array-push-push: [2]
unicorn/no-array-reduce: [2]
unicorn/no-await-expression-member: [0]
unicorn/no-await-in-promise-methods: [2]
unicorn/no-console-spaces: [0]
unicorn/no-document-cookie: [2]
unicorn/no-empty-file: [2]
unicorn/no-for-loop: [0]
unicorn/no-hex-escape: [0]
unicorn/no-instanceof-array: [0]
unicorn/no-invalid-fetch-options: [2]
unicorn/no-invalid-remove-event-listener: [2]
unicorn/no-keyword-prefix: [0]
unicorn/no-length-as-slice-end: [2]
unicorn/no-lonely-if: [2]
unicorn/no-magic-array-flat-depth: [0]
unicorn/no-negated-condition: [0]
unicorn/no-negation-in-equality-check: [2]
unicorn/no-nested-ternary: [0]
unicorn/no-new-array: [0]
unicorn/no-new-buffer: [0]
unicorn/no-null: [0]
unicorn/no-object-as-default-parameter: [0]
unicorn/no-process-exit: [0]
unicorn/no-single-promise-in-promise-methods: [2]
unicorn/no-static-only-class: [2]
unicorn/no-thenable: [2]
unicorn/no-this-assignment: [2]
unicorn/no-typeof-undefined: [2]
unicorn/no-unnecessary-await: [2]
unicorn/no-unnecessary-polyfills: [2]
unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
unicorn/no-unused-properties: [2]
unicorn/no-useless-fallback-in-spread: [2]
unicorn/no-useless-length-check: [2]
unicorn/no-useless-promise-resolve-reject: [2]
unicorn/no-useless-spread: [2]
unicorn/no-useless-switch-case: [2]
unicorn/no-useless-undefined: [0]
unicorn/no-zero-fractions: [2]
unicorn/number-literal-case: [0]
unicorn/numeric-separators-style: [0]
unicorn/prefer-add-event-listener: [2]
unicorn/prefer-array-find: [2]
unicorn/prefer-array-flat-map: [2]
unicorn/prefer-array-flat: [2]
unicorn/prefer-array-index-of: [2]
unicorn/prefer-array-some: [2]
unicorn/prefer-at: [0]
unicorn/prefer-blob-reading-methods: [2]
unicorn/prefer-code-point: [0]
unicorn/prefer-date-now: [2]
unicorn/prefer-default-parameters: [0]
unicorn/prefer-dom-node-append: [2]
unicorn/prefer-dom-node-dataset: [0]
unicorn/prefer-dom-node-remove: [2]
unicorn/prefer-dom-node-text-content: [2]
unicorn/prefer-event-target: [2]
unicorn/prefer-export-from: [0]
unicorn/prefer-global-this: [0]
unicorn/prefer-includes: [2]
unicorn/prefer-json-parse-buffer: [0]
unicorn/prefer-keyboard-event-key: [2]
unicorn/prefer-logical-operator-over-ternary: [2]
unicorn/prefer-math-min-max: [2]
unicorn/prefer-math-trunc: [2]
unicorn/prefer-modern-dom-apis: [0]
unicorn/prefer-modern-math-apis: [2]
unicorn/prefer-module: [2]
unicorn/prefer-native-coercion-functions: [2]
unicorn/prefer-negative-index: [2]
unicorn/prefer-node-protocol: [2]
unicorn/prefer-number-properties: [0]
unicorn/prefer-object-from-entries: [2]
unicorn/prefer-object-has-own: [0]
unicorn/prefer-optional-catch-binding: [2]
unicorn/prefer-prototype-methods: [0]
unicorn/prefer-query-selector: [2]
unicorn/prefer-reflect-apply: [0]
unicorn/prefer-regexp-test: [2]
unicorn/prefer-set-has: [0]
unicorn/prefer-set-size: [2]
unicorn/prefer-spread: [0]
unicorn/prefer-string-raw: [0]
unicorn/prefer-string-replace-all: [0]
unicorn/prefer-string-slice: [0]
unicorn/prefer-string-starts-ends-with: [2]
unicorn/prefer-string-trim-start-end: [2]
unicorn/prefer-structured-clone: [2]
unicorn/prefer-switch: [0]
unicorn/prefer-ternary: [0]
unicorn/prefer-text-content: [2]
unicorn/prefer-top-level-await: [0]
unicorn/prefer-type-error: [0]
unicorn/prevent-abbreviations: [0]
unicorn/relative-url-style: [2]
unicorn/require-array-join-separator: [2]
unicorn/require-number-to-fixed-digits-argument: [2]
unicorn/require-post-message-target-origin: [0]
unicorn/string-content: [0]
unicorn/switch-case-braces: [0]
unicorn/template-indent: [2]
unicorn/text-encoding-identifier-case: [0]
unicorn/throw-new-error: [2]
use-isnan: [2]
valid-typeof: [2, {requireStringLiterals: true}]
vars-on-top: [0]
wc/attach-shadow-constructor: [2]
wc/define-tag-after-class-definition: [0]
wc/expose-class-on-global: [0]
wc/file-name-matches-element: [2]
wc/guard-define-call: [0]
wc/guard-super-call: [2]
wc/max-elements-per-file: [0]
wc/no-child-traversal-in-attributechangedcallback: [2]
wc/no-child-traversal-in-connectedcallback: [2]
wc/no-closed-shadow-root: [2]
wc/no-constructor-attributes: [2]
wc/no-constructor-params: [2]
wc/no-constructor: [2]
wc/no-customized-built-in-elements: [2]
wc/no-exports-with-element: [0]
wc/no-invalid-element-name: [2]
wc/no-invalid-extends: [2]
wc/no-method-prefixed-with-on: [2]
wc/no-self-class: [2]
wc/no-typos: [2]
wc/require-listener-teardown: [2]
wc/tag-name-matches-class: [2]
yoda: [2, never]

3
.gitattributes vendored
View file

@ -4,7 +4,8 @@
/assets/*.json linguist-generated /assets/*.json linguist-generated
/public/assets/img/svg/*.svg linguist-generated /public/assets/img/svg/*.svg linguist-generated
/templates/swagger/v1_json.tmpl linguist-generated /templates/swagger/v1_json.tmpl linguist-generated
/options/fileicon/** linguist-generated
/vendor/** -text -eol linguist-vendored /vendor/** -text -eol linguist-vendored
/web_src/fomantic/build/** linguist-generated
/web_src/fomantic/_site/globals/site.variables linguist-language=Less
/web_src/js/vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored
Dockerfile.* linguist-language=Dockerfile Dockerfile.* linguist-language=Dockerfile

View file

@ -13,5 +13,5 @@ contact_links:
url: https://docs.gitea.com/help/faq url: https://docs.gitea.com/help/faq
about: Please check if your question isn't mentioned here. about: Please check if your question isn't mentioned here.
- name: Crowdin Translations - name: Crowdin Translations
url: https://translate.gitea.com url: https://crowdin.com/project/gitea
about: Translations are managed here. about: Translations are managed here.

10
.github/labeler.yml vendored
View file

@ -41,7 +41,7 @@ modifies/internal:
- ".dockerignore" - ".dockerignore"
- "docker/**" - "docker/**"
- ".editorconfig" - ".editorconfig"
- ".eslintrc.cjs" - ".eslintrc.yaml"
- ".golangci.yml" - ".golangci.yml"
- ".gitpod.yml" - ".gitpod.yml"
- ".markdownlint.yaml" - ".markdownlint.yaml"
@ -49,7 +49,7 @@ modifies/internal:
- "stylelint.config.js" - "stylelint.config.js"
- ".yamllint.yaml" - ".yamllint.yaml"
- ".github/**" - ".github/**"
- ".gitea/**" - ".gitea/"
- ".devcontainer/**" - ".devcontainer/**"
- "build.go" - "build.go"
- "build/**" - "build/**"
@ -73,9 +73,9 @@ modifies/go:
modifies/frontend: modifies/frontend:
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:
- "*.js" - "**/*.js"
- "*.ts" - "**/*.ts"
- "web_src/**" - "**/*.vue"
docs-update-needed: docs-update-needed:
- changed-files: - changed-files:

View file

@ -1,8 +1,8 @@
name: cron-licenses name: cron-licenses
on: on:
# schedule: schedule:
# - cron: "7 0 * * 1" # every Monday at 00:07 UTC - cron: "7 0 * * 1" # every Monday at 00:07 UTC
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -15,7 +15,7 @@ jobs:
with: with:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- run: make generate-gitignore - run: make generate-license generate-gitignore
timeout-minutes: 40 timeout-minutes: 40
- name: push translations to repo - name: push translations to repo
uses: appleboy/git-push-action@v0.0.3 uses: appleboy/git-push-action@v0.0.3

View file

@ -51,16 +51,14 @@ jobs:
- "options/locale/locale_en-US.ini" - "options/locale/locale_en-US.ini"
frontend: frontend:
- "*.js" - "**/*.js"
- "*.ts"
- "web_src/**" - "web_src/**"
- "tools/*.js"
- "tools/*.ts"
- "assets/emoji.json" - "assets/emoji.json"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"
- "Makefile" - "Makefile"
- ".eslintrc.cjs" - ".eslintrc.yaml"
- "stylelint.config.js"
- ".npmrc" - ".npmrc"
docs: docs:
@ -87,7 +85,6 @@ jobs:
swagger: swagger:
- "templates/swagger/v1_json.tmpl" - "templates/swagger/v1_json.tmpl"
- "templates/swagger/v1_input.json"
- "Makefile" - "Makefile"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"

View file

@ -37,7 +37,7 @@ jobs:
python-version: "3.12" python-version: "3.12"
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: pip install poetry - run: pip install poetry
@ -66,7 +66,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
@ -95,7 +95,7 @@ jobs:
go-version-file: go.mod go-version-file: go.mod
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make lint-go-windows lint-go-gitea-vet - run: make lint-go-windows lint-go-vet
env: env:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
GOOS: windows GOOS: windows
@ -137,7 +137,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
@ -186,7 +186,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend

View file

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
pgsql: pgsql:
image: postgres:14 image: postgres:12
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@ -98,7 +98,7 @@ jobs:
ports: ports:
- "9200:9200" - "9200:9200"
meilisearch: meilisearch:
image: getmeili/meilisearch:v1 image: getmeili/meilisearch:v1.2.0
env: env:
MEILI_ENV: development # disable auth MEILI_ENV: development # disable auth
ports: ports:
@ -202,6 +202,7 @@ jobs:
test-mssql: test-mssql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
# NOTE: mssql-2017 docker image will panic when run on hosts that have Ubuntu newer than 20.04
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
mssql: mssql:

View file

@ -12,9 +12,7 @@ jobs:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
test-e2e: test-e2e:
# the "test-e2e" won't pass, and it seems that there is no useful test, so skip if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
# if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
if: false
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -25,7 +23,7 @@ jobs:
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend frontend deps-backend - run: make deps-frontend frontend deps-backend

View file

@ -22,7 +22,7 @@ jobs:
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend

View file

@ -23,7 +23,7 @@ jobs:
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend

View file

@ -27,7 +27,7 @@ jobs:
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 22
cache: npm cache: npm
cache-dependency-path: package-lock.json cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend

32
.gitignore vendored
View file

@ -9,11 +9,6 @@ _test
# IntelliJ # IntelliJ
.idea .idea
.run
# IntelliJ Gateway
.uuid
# Goland's output filename can not be set manually # Goland's output filename can not be set manually
/go_build_* /go_build_*
/gitea_* /gitea_*
@ -22,9 +17,6 @@ _test
.vscode .vscode
__debug_bin* __debug_bin*
# Visual Studio
/.vs/
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c
_cgo_defun.c _cgo_defun.c
@ -42,10 +34,14 @@ _testmain.go
coverage.all coverage.all
cpu.out cpu.out
/modules/migration/bindata.* /modules/migration/bindata.go
/modules/options/bindata.* /modules/migration/bindata.go.hash
/modules/public/bindata.* /modules/options/bindata.go
/modules/templates/bindata.* /modules/options/bindata.go.hash
/modules/public/bindata.go
/modules/public/bindata.go.hash
/modules/templates/bindata.go
/modules/templates/bindata.go.hash
*.db *.db
*.log *.log
@ -83,6 +79,18 @@ cpu.out
/public/assets/fonts /public/assets/fonts
/public/assets/licenses.txt /public/assets/licenses.txt
/vendor /vendor
/web_src/fomantic/node_modules
/web_src/fomantic/build/*
!/web_src/fomantic/build/semantic.js
!/web_src/fomantic/build/semantic.css
!/web_src/fomantic/build/themes
/web_src/fomantic/build/themes/*
!/web_src/fomantic/build/themes/default
/web_src/fomantic/build/themes/default/assets/*
!/web_src/fomantic/build/themes/default/assets/fonts
/web_src/fomantic/build/themes/default/assets/fonts/*
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
/VERSION /VERSION
/.air /.air
/.go-licenses /.go-licenses

View file

@ -1,9 +1,7 @@
version: "2"
output:
sort-order:
- file
linters: linters:
default: none enable-all: false
disable-all: true
fast: false
enable: enable:
- bidichk - bidichk
- depguard - depguard
@ -11,170 +9,139 @@ linters:
- errcheck - errcheck
- forbidigo - forbidigo
- gocritic - gocritic
- govet
- ineffassign
- mirror
- nakedret
- nolintlint
- perfsprint
- revive
- staticcheck
- testifylint
- unconvert
- unparam
- unused
- usestdlibvars
- usetesting
- wastedassign
settings:
depguard:
rules:
main:
deny:
- pkg: encoding/json
desc: use gitea's modules/json instead of encoding/json
- pkg: github.com/unknwon/com
desc: use gitea's util and replacements
- pkg: io/ioutil
desc: use os or io instead
- pkg: golang.org/x/exp
desc: it's experimental and unreliable
- pkg: code.gitea.io/gitea/modules/git/internal
desc: do not use the internal package, use AddXxx function instead
- pkg: gopkg.in/ini.v1
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system
nolintlint:
allow-unused: false
require-explanation: true
require-specific: true
gocritic:
disabled-checks:
- ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
revive:
severity: error
rules:
- name: atomic
- name: bare-return
- name: blank-imports
- name: constant-logical-expr
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: duplicated-imports
- name: empty-lines
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
- name: identical-branches
- name: if-return
- name: increment-decrement
- name: indent-error-flow
- name: modifies-value-receiver
- name: package-comments
- name: range
- name: receiver-naming
- name: redefines-builtin-id
- name: string-of-int
- name: superfluous-else
- name: time-naming
- name: unconditional-recursion
- name: unexported-return
- name: unreachable-code
- name: var-declaration
- name: var-naming
arguments:
- [] # AllowList - do not remove as args for the rule are positional and won't work without lists first
- [] # DenyList
- - skip-package-name-checks: true # supress errors from underscore in migration packages
staticcheck:
checks:
- all
- -ST1003
- -ST1005
- -QF1001
- -QF1006
- -QF1008
testifylint:
disable:
- go-require
- require-error
usetesting:
os-temp-dir: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- dupl
- errcheck
- gocyclo
- gosec
- staticcheck
- unparam
path: _test\.go
- linters:
- dupl
- errcheck
- gocyclo
- gosec
path: models/migrations/v
- linters:
- forbidigo
path: cmd
- linters:
- dupl
text: (?i)webhook
- linters:
- gocritic
text: (?i)`ID' should not be capitalized
- linters:
- deadcode
- unused
text: (?i)swagger
- linters:
- staticcheck
text: (?i)argument x is overwritten before first use
- linters:
- gocritic
text: '(?i)commentFormatting: put a space between `//` and comment text'
- linters:
- gocritic
text: '(?i)exitAfterDefer:'
paths:
- node_modules
- public
- web_src
- third_party$
- builtin$
- examples$
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gofmt - gofmt
- gofumpt - gofumpt
settings: - gosimple
gofumpt: - govet
extra-rules: true - ineffassign
exclusions: - nakedret
generated: lax - nolintlint
paths: - revive
- node_modules - staticcheck
- public - stylecheck
- web_src - tenv
- third_party$ - testifylint
- builtin$ - typecheck
- examples$ - unconvert
- unused
- unparam
- wastedassign
run: run:
timeout: 10m timeout: 10m
output:
sort-results: true
sort-order: [file]
show-stats: true
linters-settings:
testifylint:
disable:
- go-require
- require-error
stylecheck:
checks: ["all", "-ST1005", "-ST1003"]
nakedret:
max-func-lines: 0
gocritic:
disabled-checks:
- ifElseChain
- singleCaseSwitch # Every time this occurred in the code, there was no other way.
revive:
severity: error
rules:
- name: atomic
- name: bare-return
- name: blank-imports
- name: constant-logical-expr
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: duplicated-imports
- name: empty-lines
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
- name: identical-branches
- name: if-return
- name: increment-decrement
- name: indent-error-flow
- name: modifies-value-receiver
- name: package-comments
- name: range
- name: receiver-naming
- name: redefines-builtin-id
- name: string-of-int
- name: superfluous-else
- name: time-naming
- name: unconditional-recursion
- name: unexported-return
- name: unreachable-code
- name: var-declaration
- name: var-naming
gofumpt:
extra-rules: true
depguard:
rules:
main:
deny:
- pkg: encoding/json
desc: use gitea's modules/json instead of encoding/json
- pkg: github.com/unknwon/com
desc: use gitea's util and replacements
- pkg: io/ioutil
desc: use os or io instead
- pkg: golang.org/x/exp
desc: it's experimental and unreliable
- pkg: code.gitea.io/gitea/modules/git/internal
desc: do not use the internal package, use AddXxx function instead
- pkg: gopkg.in/ini.v1
desc: do not use the ini package, use gitea's config system instead
- pkg: gitea.com/go-chi/cache
desc: do not use the go-chi cache package, use gitea's cache system
issues:
max-issues-per-linter: 0
max-same-issues: 0
exclude-dirs: [node_modules, public, web_src]
exclude-case-sensitive: true
exclude-rules:
- path: _test\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- unparam
- staticcheck
- path: models/migrations/v
linters:
- gocyclo
- errcheck
- dupl
- gosec
- path: cmd
linters:
- forbidigo
- text: "webhook"
linters:
- dupl
- text: "`ID' should not be capitalized"
linters:
- gocritic
- text: "swagger"
linters:
- unused
- deadcode
- text: "argument x is overwritten before first use"
linters:
- staticcheck
- text: "commentFormatting: put a space between `//` and comment text"
linters:
- gocritic
- text: "exitAfterDefer:"
linters:
- gocritic

View file

@ -1,6 +1,9 @@
*.min.css *.min.css
*.min.js *.min.js
/assets/*.json /assets/*.json
/modules/options/bindata.go
/modules/public/bindata.go
/modules/templates/bindata.go
/options/gitignore /options/gitignore
/options/license /options/license
/public/assets /public/assets

View file

@ -1,2 +0,0 @@
Unknwon <u@gogs.io> <joe2010xtmf@163.com>
Unknwon <u@gogs.io> 无闻 <u@gogs.io>

View file

@ -4,376 +4,6 @@ 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 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). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.24.0](https://github.com/go-gitea/gitea/releases/tag/1.24.0) - 2025-05-26
* BREAKING
* Make Gitea always use its internal config, ignore `/etc/gitconfig` (#33076)
* Improve log format (#33814)
* Fix markdown render behaviors (#34122)
* Add package version api endpoints (#34173)
* FEATURES
* Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187)
* Add fullscreen mode as a more efficient operation way to view projects (#34081)
* Add anonymous access support for private/unlisted repositories (#34051)
* Support public code/issue access for private repositories (#33127)
* Add middleware for request prioritization (#33951)
* Add cli flags LDAP group configuration (#33933)
* Add file tree to file view page (#32721)
* Add material icons for file list (#33837)
* Artifacts download api for artifact actions v4 (#33510)
* Support choose email when creating a commit via web UI (#33432)
* Add basic auth support to rss/atom feeds (#33371)
* Add sorting by exclusive labels (issue priority) (#33206)
* Add sub issue list support (#32940)
* Private README.md for organization (#32872)
* Email option to embed images as base64 instead of link (#32061)
* Option to delay conflict checking of old pull requests until page view (#27779)
* Worktime tracking for the organization level (#19808)
* PERFORMANCE
* Add cache for common package queries (#22491)
* Move issue pin to an standalone table for querying performance (#33452)
* Improve commits list performance to reduce unnecessary database queries (#33528)
* Optimize total count of feed when loading activities in user dashboard. (#33841)
* Optimize heatmap query (#33853)
* Only use prev and next buttons for pagination on user dashboard (#33981)
* Improve pull request list API performance (#34052)
* Cache GPG keys, emails and users when list commits (#34086)
* Refactor Git Attribute & performance optimization (#34154)
* Performance optimization for tags synchronization (#34355) #34522
* ENHANCEMENTS
* Code
* Display when a release attachment was uploaded (#34261)
* Support creating relative link to raw path in markdown (#34105)
* Improve code block readability and isolate copy button (#34009)
* Improve repository commit view (#33877)
* Full-file syntax highlighting for diff pages (#33766)
* Clone repository with Tea CLI (#33725)
* Improve sync fork behavior (#33319)
* Make git clone URL could use current signed-in user (#33091)
* Add submodule diff links (#33097)
* Link to tree views of submodules if possible (#33424)
* Only keep popular licenses (#33832)
* De-emphasize signed commits (#31160)
* Actions
* Add flat-square action badge style (#34062)
* Update action status badge layout (#34018)
* Download actions job logs from API (#33858)
* Always show the "rerun" button for action jobs (#33692)
* Add auto-expanding running actions step (#30058)
* Update status check for all supported on.pull_request.types in Gitea (#33117)
* Workflow_dispatch use workflow from trigger branch (#33098)
* Add action auto-scroll (#30057)
* Add workflow_job webhook (#33694)
* Add a button editing action secret (#34462)
* Pull Request
* Auto expand "New PR" form (#33971)
* Mark parent directory as viewed when all files are viewed (#33958)
* Show info about maintainers are allowed to edit a PR (#33738)
* Automerge supports deleting branch automatically after merging (#32343)
* Add additional command hints for PowerShell & CMD (#33548)
* Issues
* Allow filtering issues by any assignee (#33343)
* Show warning on navigation if currently editing comment or title (#32920)
* Make tracked time representation display as hours (#33315)
* Add No Results Prompt Message on Issue List Page (#33699)
* Add sort option recentclose for issues and pulls (#34525) #34539
* Packages
* Link to nuget dependencies (#26554)
* Add composor source field (#33502)
* Administration
* Improve navbar: add "admin" tip, add "active" style (#32927)
* Add a option "--user-type bot" to admin user create, improve role display (#27885)
* Improve admin user view page (#33735)
* Support performance trace (#32973)
* Change pprof labels to be prometheus compatible (#32865)
* Allow admins and org owners to change org member public status (#28294)
* Optimize the installation page (#32994)
* Make public URL generation configurable (#34250)
* Add a --fullname arg to gitea admin user create. (#34241)
* Others
* Improve oauth2 error handling (#33969)
* Fail mirroring more gracefully (#34002)
* Align User Details Page Header Layout with Design Specifications (#34192)
* Webhook add X-Gitea-Hook-Installation-Target-Type Header (#33752)
* Optimize the dashboard (#32990)
* Improve button layout on small screens (#33633)
* Add cropping support when modifying the user/org/repo avatar (#33498)
* Make ROOT_URL support using request Host header (#32564)
* Add `show more` organizations icon in user's profile (#32986)
* Introduce `--page-space-bottom` at 64px (#30692)
* Improve theme display (#30671)
* Add alphabetical project sorting (#33504)
* Add global lock for migrations to make upgrade more safe with multiple replications (#33706)
* Add descriptions for private repo public access settings and improve the UI (#34057)
* API
* Actions Runner rest api (#33873)
* Inclusion of rename organization api (#33303)
* Add API to support link package to repository and unlink it (#33481)
* Add API endpoint to request contents of multiple files simultaniously (#34139)
* Actions artifacts API list/download check status upload confirmed (#34273)
* Add API routes to lock and unlock issues (#34165)
* Fix some user name usages (#33689)
* Allow filtering /repos/{owner}/{repo}/pulls by target base branch queryparam (#33684)
* Improve swagger generation (#33664)
* Support Ephemeral action runners (#33570)
* Support workflow event dispatch via API (#33545)
* Support workflow event dispatch via API (#32059)
* Added Description Field for Secrets and Variables (#33526)
* Reject star-related requests if stars are disabled (#33208)
* Let API create and edit system webhooks, attempt 2 (#33180)
* Use `Project-URL` metadata field to get a PyPI package's homepage URL (#33089)
* Add `last_committer_date` and `last_author_date` for file contents API (#32921)
* REFACTORS
* Remove context from git struct (#33793)
* Refactor admin/common.ts (#33788)
* Refactor repo-settings.ts (#33785)
* Refactor repo-issue.ts (#33784)
* Small refactor to reduce unnecessary database queries and remove duplicated functions (#33779)
* Refactor initRepoBranchTagSelector to use new init framework (#33776)
* Refactor buttons to use new init framework (#33774)
* Refactor markup and pdf-viewer to use new init framework (#33772)
* Refactor error system (#33771)
* Refactor mail code (#33768)
* Update TypeScript types (#33799)
* Refactor older tests to use testify (#33140)
* Move notifywatch to service layer (#33825)
* Decouple context from repository related structs (#33823)
* Remove context from mail struct (#33811)
* Refactor dropdown ellipsis (#34123)
* Refactor functions to reduce repopath expose (#33892)
* Refactor repo-diff.ts (#33746)
* Refactor web route handler (#33488)
* Refactor user & avatar (#33433)
* Refactor user package (#33423)
* Refactor decouple context from migration structs (#33399)
* Refactor context flash msg and global variables (#33375)
* Refactor response writer & access logger (#33323)
* Refactor ref type (#33242)
* Refactor context repository (#33202)
* Refactor legacy JS (#33115)
* Refactor legacy line-number and scroll code (#33094)
* Refactor env var related code (#33075)
* Move SetMerged to service layer (#33045)
* Merge updatecommentattachment functions (#33044)
* Refactor pull-request compare&create page (#33071)
* Refactor repo-new.ts (#33070)
* Refactor pagination (#33037)
* Refactor tests (#33021)
* Refactor markup render to fix various path problems (#34114)
* Refactor Branch struct in package modules/git (#33980)
* Don't create duplicated functions for code repositories and wiki repositories (#33924)
* Move git references checking to gitrepo packages to reduce expose of repository path (#33891)
* Refactor cache-control (#33861)
* Decouple diff stats query from actual diffing (#33810)
* Move part of updating protected branch logic to service layer (#33742)
* Decouple Batch from git.Repository to simplify usage without requiring the creation of a Repository struct. (#34001)
* Refactor tmpl and blob_excerpt (#32967)
* Refactor template & test related code (#32938)
* Refactor db package and remove unnecessary `DumpTables` (#32930)
* Refactor pprof labels and process desc (#32909)
* Refactor repo-projects.ts (#32892)
* Refactor getpatch/getdiff functions and remove unnecessary fallback (#32817)
* Uniform all temporary directories and allow customizing temp path (#32352)
* Remove context from retry downloader (#33871)
* Refactor global init code and add more comments (#33755)
* Remove some unnecessary template helpers (#33069)
* Move and rename UpdateRepository (#34136)
* Move hooks function to gitrepo and reduce expose repopath (#33890)
* Add abstraction layer to delete repository from disk (#33879)
* Add abstraction layer to check if the repository exists on disk (#33874)
* Move ParseCommitWithSSHSignature to service layer (#34087)
* Move duplicated functions (#33977)
* Extract code to their own functions for push update (#33944)
* Move gitgraph from modules to services layer (#33527)
* Move commits signature and verify functions to service layers (#33605)
* Use `CloseIssue` and `ReopenIssue` instead of `ChangeStatus` (#32467)
* Refactor arch route handlers (#32993)
* Refactor "string truncate" (#32984)
* Refactor arch route handlers (#32972)
* Clarify path param naming (#32969)
* Refactor request context (#32956)
* Move some errors to their own sub packages (#32880)
* Move RepoTransfer from models to models/repo sub package (#32506)
* Move delete deploy keys into service layer (#32201)
* Refactor webhook events (#33337)
* Move some Actions related functions from `routers` to `services` (#33280)
* Refactor RefName (#33234)
* Refactor context RefName and RepoAssignment (#33226)
* Refactor repository transfer (#33211)
* Refactor error system (#33626)
* Refactor error system (#33610)
* Refactor package (routes and error handling, npm peer dependency) (#33111)
* Use test context in tests and new loop system in benchmarks (#33648)
* Some small refactors (#33144)
* Simplify context ref name (#33267)
* BUGFIXES
* Fix some dropdown problems on the issue sidebar (#34308) #34327
* Do not return archive download URLs in API if downloads are disabled (#34324) #34338
* Fix LFS files being editable in web UI (#34356) #34362
* Fix only text/* being viewable in web UI (#34374) #34378
* Fix LFS file not stored in LFS when uploaded/edited via API or web UI (#34367)
* Grey out expired artifact on Artifacts list (#34314) #34404
* Fix incorrect divergence cache after switching default branch (#34370) #34406
* Refactor commit message rendering and fix bugs (#34412) #34414
* Merge and tweak markup editor expander CSS (#34409) #34415
* Fix GetUsersByEmails (#34423) #34425
* Only git operations should update last changed of a repository (#34388) #34427
* Fix comment textarea scroll issue in Firefox (#34438) #34446
* Fix repo broken check (#34444) #34452
* Fix remove org user failure on mssql (#34449) #34453
* Fix Workflow run Not Found page (#34459) #34466
* When updating comment, if the content is the same, just return and not update the database (#34422) #34464
* Fix project board view (#34470) #34475
* Fix get / delete runner to use consistent http 404 and 500 status (#34480) #34488
* Fix url validation in webhook add/edit API (#34492) #34496
* Fix edithook api can not update package, status and workflow_job events (#34495) #34499
* Fix ephemeral runner deletion (#34447) #34513
* Don't display error log when .git-blame-ignore-revs doesn't exist (#34457)
* Only allow admins to rename default/protected branches (#33276)
* Improve "lock conversation" UI (#34207)
* Fix incorrect file links (#34189)
* Optimize Overflow Menu (#34183)
* Check user/org repo limit instead of doer (#34147)
* Make markdown render match GitHub's behavior (#34129)
* Fix team permission (#34128)
* Correctly handle submodule view and avoid throwing 500 error (#34121)
* Fix users being able bypass limits with repo transfers (#34031)
* Avoid creating unnecessary temporary cat file sub process (#33942)
* Refactor organization menu (#33928)
* Fix various Fomantic UI and htmx problems (#33851)
* Fix 500 error when error occurred in migration page (#33256)
* Validate that the tag doesn't exist when creating a tag via the web (#33241)
* Add missed transaction on setmerged (#33079)
* Rework create/fork/adopt/generate repository to make sure resources will be cleanup once failed (#31035)
* Valid email address should only start with alphanumeric (#28174)
* Fix webhook url (#34186)
* Fix "toAbsoluteLocaleDate" test when system locale is not en-US (#33939)
* Fix file name could not be searched if the file was not a text file when using the Bleve indexer (#33959)
* Fix cannot delete runners via the modal dialog (#33895)
* Fix unpin hint on the pinned pull requests (#33207)
* Fix parentCommit invalid memory address or nil pointer dereference. (#33204)
* Fix comment header padding (#33377)
* Fix some migration and repo name problems (#33986)
* Fix various trivial frontend problems (#34263)
* Fix Set Email Preference dropdown and button placement (#34255)
* Fix quoted replies incorrectly render user input as part of the quote (#34216)
* Fix button alignments and remove unnecessary styles (#34206)
* Restore form inputs on organization create error (#34201)
* Try to fix ACME (3rd) (#33807)
* Fix incorrect ref "blob" (#33240)
* Fix dynamic content loading init problem (#33748)
* Fix git empty check and HEAD request (#33690)
* Fix Untranslated Text on Actions Page (#33635)
* Fix issue label delete incorrect labels webhook payload (#34575)
* Fix incorrect page navigation with up and down arrow on last item of dashboard repos (#34570)
* Fix/improve avatar sync from LDAP (#34573)
* Fix some trivial problems (#34579)
* Retain issue sort type when a keyword search is introduced (#34559)
* Always use an empty line to separate the commit message and trailer (#34512)
* Fix line-button issue after file selection in file tree (#34574)
* Fix doctor deleting orphaned issues attachments (#34142)
* Add webhook assigning test and fix possible bug (#34420)
* Fix possible nil description of pull request when migrating from CodeCommit (#34541)
* Refactor commit reader (#34542)
* Fix possible pull request broken when leave the page immediately after clicking the update button #34509
* Ignore "Close" error when uploading container blob (#34620)
* Fix missed merge commit sha and time when migrating from codecommit (#34645)
* Fix GetUsersByEmails (#34643)
* Misc CSS fixes (#34638)
* Add codecommit to supported services in api docs (#34626)
* Validate hex colors when creating/editing labels (#34623)
* Fix possible pull request broken when leave the page immediately after clicking the update button (#34509)
* Fix margin issue in markup paragraph rendering (#34599)
* Fix migration pull request title too long (#34577)
* Fix footnote jump behavior on the issue page. (#34621)
* Fix "oras" OCI client compatibility (#34666)
* Fix last admin check when syncing users (#34649)
* Fix skip paths check on tag push events in workflows (#34602) #34670
* MISC
* Bump to alpine 3.22 (#34613)
* Make pull request and issue history more compact (#34588)
* Run integration tests against postgres 14 (#34514) #34536
* Enable addtional linters (#34085)
* Enable testifylint rules (#34075)
* Enable staticcheck QFxxxx rules (#34064)
* Improve Actions test (#32883)
* Drop fomantic build (#33845)
* Go1.24 (#33562)
* Run yamllint with strict mode, fix issue (#33551)
* Disable cron task to update license (#33486)
* Optimize makefile help information generation (#33390)
* Convert github.com/xanzy/go-gitlab into gitlab.com/gitlab-org/api/client-go (#33126)
* Add missed changelogs (#33649)
* Update .changelog file to add performance label group (#33472)
* Add missing POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES in app.example.ini (#33363)
* Update README screenshots (#33347)
* Update unrs-resolver (#34279)
* Update go&js dependencies (#34262)
* Optimize the calling code of queryElems (#34235)
* Update protected_branch.tmpl (#34193)
* Feat/optimize span svg layout (#34185)
* Set MERMAID_MAX_SOURCE_CHARACTERS to 50000 (#34152)
* Update JS and PY deps (#34143)
* Add Chinese translations for README files (#34132)
* Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126)
* Clarify ownership in password change error messages (#34092)
* Add toggleClass function in dom.ts (#34063)
* Update to golangci-lint v2 (#34054)
* Update Makefile test comments (#34013)
* Update go mod dependencies (#33988)
* Use filepath.Join instead of path.Join for file system file operations (#33978)
* Prepare common tmpl functions in a middleware (#33957)
* Remove unused or abused styles (#33918)
* Update JS and PY deps, misc tweaks (#33903)
* Try to figure out attribute checker problem (#33901)
* Add lock for a repository pull mirror (#33876)
* Fine tune push mirror UI (#33866)
* Improve issue & code search (#33860)
* Use pullrequestlist instead of []*pullrequest (#33765)
* Upgrade act to 0.261.4 and actions-proto-go to v0.4.1 (#33760)
* Align sidebar gears to the right (#33721)
* Update Go dependencies (skip blevesearch, meilisearch) (#33655)
* Add migrations and doctor fixes (#33556)
* Remove "class-name" from svg icon (#33540)
* Update MAINTAINERS (#33529)
* Add "No data available" display when list is empty (#33517)
* Use `git diff-tree` for `DiffFileTree` on diff pages (#33514)
* Give organisation members access to organisation feeds (#33508)
* Update feishu icon (#33470)
* Hide/disable unusable UI elements when a repository is archived (#33459)
* Update `@github/text-expander-element` to 2.9.0 (#33435)
* Do not access GitRepo when a repo is being created (#33380)
* Fix incorrect ref usages (#33301)
* Prepare for support performance trace (#33286)
* Enable Typescript `noImplicitThis` (#33250)
* Remove unused CSS styles and move some styles to proper files (#33217)
* Add .run to gitignore (#33175)
* Fix typo in gitea downloader test and add missing codebase in `ToGitServiceType` (#33146)
* Remove extended glob pattern from branch protection UI (#33125)
* Clean up legacy form CSS styles (#33081)
* Unset XDG_HOME_CONFIG as gitea manages configuration locations (#33067)
* Add IntelliJ Gateway's .uuid to gitignore (#33052)
* User facing messages for AGit errors (#33012)
* Always show assignees on right (#33006)
* Fix eslint (#33002)
* Update JS dependencies (#32914)
* Bump x/net (#32896) (#32900)
* Only activity tab needs heatmap data loading (#34652)
## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11 ## [1.23.8](https://github.com/go-gitea/gitea/releases/tag/1.23.8) - 2025-05-11
* SECURITY * SECURITY
@ -449,7 +79,7 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Try to fix ACME (3rd) (#33807) (#33808) * Try to fix ACME (3rd) (#33807) (#33808)
* Fix incorrect code search indexer options (#33992) #33999 * Fix incorrect code search indexer options (#33992) #33999
## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-04 ## [1.23.5](https://github.com/go-gitea/gitea/releases/tag/v1.23.5) - 2025-03-03
* SECURITY * SECURITY
* Bump x/oauth2 & x/crypto (#33704) (#33727) * Bump x/oauth2 & x/crypto (#33704) (#33727)
@ -469,7 +99,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Git graph: don't show detached commits (#33645) (#33650) * Git graph: don't show detached commits (#33645) (#33650)
* Use MatchPhraseQuery for bleve code search (#33628) * Use MatchPhraseQuery for bleve code search (#33628)
* Adjust appearence of commit status webhook (#33778) #33789 * Adjust appearence of commit status webhook (#33778) #33789
* Upgrade golang net from 0.35.0 -> 0.36.0 (#33795) #33796
## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16 ## [1.23.4](https://github.com/go-gitea/gitea/releases/tag/v1.23.4) - 2025-02-16
@ -952,36 +581,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Use -s -w ldflags for release artifacts (#33041) #33042 * Use -s -w ldflags for release artifacts (#33041) #33042
* Remove aws go sdk package dependency (#33029) #33047 * Remove aws go sdk package dependency (#33029) #33047
## [1.22.6](https://github.com/go-gitea/gitea/releases/tag/v1.22.6) - 2024-12-12
* SECURITY
* Fix misuse of PublicKeyCallback(#32810)
* BUGFIXES
* Fix lfs migration (#32812) (#32818)
* Add missing two sync feed for refs/pull (#32815)
* TESTING
* Avoid MacOS keychain dialog in integration tests (#32813) (#32816)
## [1.22.5](https://github.com/go-gitea/gitea/releases/tag/v1.22.5) - 2024-12-11
* SECURITY
* Upgrade crypto library (#32791)
* Fix delete branch perm checking (#32654) (#32707)
* BUGFIXES
* Add standard-compliant route to serve outdated R packages (#32783) (#32789)
* Fix internal server error when updating labels without write permission (#32776) (#32785)
* Add Swift login endpoint (#32693) (#32701)
* Fix fork page branch selection (#32711) (#32725)
* Fix word overflow in file search page (#32695) (#32699)
* Fix gogit `GetRefCommitID` (#32705) (#32712)
* Fix race condition in mermaid observer (#32599) (#32673)
* Fixe a keystring misuse and refactor duplicates keystrings (#32668) (#32792)
* Bump relative-time-element to v4.4.4 (#32739)
* PERFORMANCE
* Make wiki pages visit fast (#32732) (#32745)
* MISC
* Don't create action when syncing mirror pull refs (#32659) (#32664)
## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14 ## [1.22.4](https://github.com/go-gitea/gitea/releases/tag/v1.22.4) - 2024-11-14
* SECURITY * SECURITY

View file

@ -182,7 +182,7 @@ Here's how to run the test suite:
## Translation ## Translation
All translation work happens on [Crowdin](https://translate.gitea.com). All translation work happens on [Crowdin](https://crowdin.com/project/gitea).
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini). The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
It is synced regularly with Crowdin. \ It is synced regularly with Crowdin. \
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \ Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \

View file

@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/code.gitea.io/gitea/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000

View file

@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/code.gitea.io/gitea/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@ -52,7 +52,6 @@ RUN apk --no-cache add \
git \ git \
curl \ curl \
gnupg \ gnupg \
openssh-keygen \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \

View file

@ -31,6 +31,7 @@ Gary Kim <gary@garykim.dev> (@gary-kim)
Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k) Guillermo Prandi <gitea.maint@mailfilter.com.ar> (@guillep2k)
Mura Li <typeless@ctli.io> (@typeless) Mura Li <typeless@ctli.io> (@typeless)
6543 <6543@obermui.de> (@6543) 6543 <6543@obermui.de> (@6543)
jaqra <jaqra@hotmail.com> (@jaqra)
David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson) David Svantesson <davidsvantesson@gmail.com> (@davidsvantesson)
a1012112796 <1012112796@qq.com> (@a1012112796) a1012112796 <1012112796@qq.com> (@a1012112796)
Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise) Karl Heinz Marbaise <kama@soebes.de> (@khmarbaise)
@ -62,6 +63,3 @@ Yu Liu <1240335630@qq.com> (@HEREYUA)
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb) Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
Rowan Bohde <rowan.bohde@gmail.com> (@bohde) Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
hiifong <i@hiif.ong> (@hiifong) hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci)
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)

286
Makefile
View file

@ -23,21 +23,20 @@ SHASUM ?= shasum -a 256
HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes)
COMMA := , COMMA := ,
XGO_VERSION := go-1.24.x XGO_VERSION := go-1.23.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/air-verse/air@v1
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.19.1 GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0
GOPLS_MODERNIZE_PACKAGE ?= golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.19.1
DOCKER_IMAGE ?= gitea/gitea DOCKER_IMAGE ?= gitea/gitea
DOCKER_TAG ?= latest DOCKER_TAG ?= latest
@ -74,13 +73,13 @@ EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1) MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1)
MAKE_EVIDENCE_DIR := .make_evidence MAKE_EVIDENCE_DIR := .make_evidence
GOTESTFLAGS ?=
ifeq ($(RACE_ENABLED),true) ifeq ($(RACE_ENABLED),true)
GOFLAGS += -race GOFLAGS += -race
GOTESTFLAGS += -race GOTESTFLAGS += -race
endif endif
STORED_VERSION_FILE := VERSION STORED_VERSION_FILE := VERSION
HUGO_VERSION ?= 0.111.3
GITHUB_REF_TYPE ?= branch GITHUB_REF_TYPE ?= branch
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
@ -115,12 +114,15 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/r
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/)) 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/...) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...)
FOMANTIC_WORK_DIR := web_src/fomantic
WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f)
WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_CONFIGS := webpack.config.js tailwind.config.js
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts
BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.* BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go
@ -137,19 +139,25 @@ TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify
TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR) TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMANTIC_WORK_DIR)/node_modules $(DIST) $(MAKE_EVIDENCE_DIR) $(AIR_TMP_DIR) $(GO_LICENSE_TMP_DIR)
GO_DIRS := build cmd models modules routers services tests GO_DIRS := build cmd models modules routers services tests
WEB_DIRS := web_src/js web_src/css WEB_DIRS := web_src/js web_src/css
ESLINT_FILES := web_src/js tools *.js *.ts *.cjs tests/e2e ESLINT_FILES := web_src/js tools *.js *.ts tests/e2e
STYLELINT_FILES := web_src/css web_src/js/components/*.vue STYLELINT_FILES := web_src/css web_src/js/components/*.vue
SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml)) $(filter-out tools/misspellings.csv, $(wildcard tools/*)) SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) templates options/locale/locale_en-US.ini .github $(filter-out CHANGELOG.md, $(wildcard *.go *.js *.md *.yml *.yaml *.toml))
EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini EDITORCONFIG_FILES := templates .github/workflows options/locale/locale_en-US.ini
GO_SOURCES := $(wildcard *.go) GO_SOURCES := $(wildcard *.go)
GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go") GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go)
GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES += $(GENERATED_GO_DEST)
GO_SOURCES_NO_BINDATA := $(GO_SOURCES)
ifeq ($(filter $(TAGS_SPLIT),bindata),bindata)
GO_SOURCES += $(BINDATA_DEST)
GENERATED_GO_DEST += $(BINDATA_DEST)
endif
# Force installation of playwright dependencies by setting this flag # Force installation of playwright dependencies by setting this flag
ifdef DEPS_PLAYWRIGHT ifdef DEPS_PLAYWRIGHT
@ -157,8 +165,10 @@ ifdef DEPS_PLAYWRIGHT
endif endif
SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC := templates/swagger/v1_json.tmpl
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
SWAGGER_EXCLUDE := code.gitea.io/sdk SWAGGER_EXCLUDE := code.gitea.io/sdk
SWAGGER_NEWLINE_COMMAND := -e '$$a\'
TEST_MYSQL_HOST ?= mysql:3306 TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_DBNAME ?= testgitea TEST_MYSQL_DBNAME ?= testgitea
@ -179,11 +189,67 @@ TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
all: build all: build
.PHONY: help .PHONY: help
help: Makefile ## print Makefile help information. help:
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m[TARGETS] default target: build\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 }' Makefile #$(MAKEFILE_LIST) @echo "Make Routines:"
@printf " \033[36m%-46s\033[0m %s\n" "test-e2e[#TestSpecificName]" "test end to end using playwright" @echo " - \"\" equivalent to \"build\""
@printf " \033[36m%-46s\033[0m %s\n" "test[#TestSpecificName]" "run unit test" @echo " - build build everything"
@printf " \033[36m%-46s\033[0m %s\n" "test-sqlite[#TestSpecificName]" "run integration test for sqlite" @echo " - frontend build frontend files"
@echo " - backend build backend files"
@echo " - watch watch everything and continuously rebuild"
@echo " - watch-frontend watch frontend files and continuously rebuild"
@echo " - watch-backend watch backend files and continuously rebuild"
@echo " - clean delete backend and integration files"
@echo " - clean-all delete backend, frontend and integration files"
@echo " - deps install dependencies"
@echo " - deps-frontend install frontend dependencies"
@echo " - deps-backend install backend dependencies"
@echo " - deps-tools install tool dependencies"
@echo " - deps-py install python dependencies"
@echo " - lint lint everything"
@echo " - lint-fix lint everything and fix issues"
@echo " - lint-actions lint action workflow files"
@echo " - lint-frontend lint frontend files"
@echo " - lint-frontend-fix lint frontend files and fix issues"
@echo " - lint-backend lint backend files"
@echo " - lint-backend-fix lint backend files and fix issues"
@echo " - lint-go lint go files"
@echo " - lint-go-fix lint go files and fix issues"
@echo " - lint-go-vet lint go files with vet"
@echo " - lint-go-gopls lint go files with gopls"
@echo " - lint-js lint js files"
@echo " - lint-js-fix lint js files and fix issues"
@echo " - lint-css lint css files"
@echo " - lint-css-fix lint css files and fix issues"
@echo " - lint-md lint markdown files"
@echo " - lint-swagger lint swagger files"
@echo " - lint-templates lint template files"
@echo " - lint-yaml lint yaml files"
@echo " - lint-spell lint spelling"
@echo " - lint-spell-fix lint spelling and fix issues"
@echo " - checks run various consistency checks"
@echo " - checks-frontend check frontend files"
@echo " - checks-backend check backend files"
@echo " - test test everything"
@echo " - test-frontend test frontend files"
@echo " - test-backend test backend files"
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
@echo " - update update js and py dependencies"
@echo " - update-js update js dependencies"
@echo " - update-py update py dependencies"
@echo " - webpack build webpack files"
@echo " - svg build svg files"
@echo " - fomantic build fomantic files"
@echo " - generate run \"go generate\""
@echo " - fmt format the Go code"
@echo " - generate-license update license files"
@echo " - generate-gitignore update gitignore files"
@echo " - generate-manpage generate manpage"
@echo " - generate-swagger generate the swagger spec from code comments"
@echo " - swagger-validate check if the swagger spec is valid"
@echo " - go-licenses regenerate go licenses"
@echo " - tidy run go mod tidy"
@echo " - test[\#TestSpecificName] run unit test"
@echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite"
.PHONY: go-check .PHONY: go-check
go-check: go-check:
@ -214,12 +280,12 @@ node-check:
fi fi
.PHONY: clean-all .PHONY: clean-all
clean-all: clean ## delete backend, frontend and integration files clean-all: clean
rm -rf $(WEBPACK_DEST_ENTRIES) node_modules rm -rf $(WEBPACK_DEST_ENTRIES) node_modules
.PHONY: clean .PHONY: clean
clean: ## delete backend and integration files clean:
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
integrations*.test \ integrations*.test \
e2e*.test \ e2e*.test \
tests/integration/gitea-integration-* \ tests/integration/gitea-integration-* \
@ -230,7 +296,7 @@ clean: ## delete backend and integration files
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/
.PHONY: fmt .PHONY: fmt
fmt: ## format the Go and template code fmt:
@GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' @GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}'
$(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl'))
@# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only @# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only
@ -245,20 +311,7 @@ fmt-check: fmt
@diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \ @diff=$$(git diff --color=always $(GO_SOURCES) templates $(WEB_DIRS)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \ echo "Please run 'make fmt' and commit the result:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
exit 1; \
fi
.PHONY: fix
fix: ## apply automated fixes to Go code
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./...
.PHONY: fix-check
fix-check: fix
@diff=$$(git diff --color=always $(GO_SOURCES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fix' and commit the result:"; \
printf "%s" "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@ -272,95 +325,95 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
endif endif
.PHONY: generate-swagger .PHONY: generate-swagger
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments generate-swagger: $(SWAGGER_SPEC)
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT) $(SWAGGER_SPEC): $(GO_SOURCES_NO_BINDATA)
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)' $(GO) run $(SWAGGER_PACKAGE) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)'
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)'
.PHONY: swagger-check .PHONY: swagger-check
swagger-check: generate-swagger swagger-check: generate-swagger
@diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \ @diff=$$(git diff --color=always '$(SWAGGER_SPEC)'); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make generate-swagger' and commit the result:"; \ echo "Please run 'make generate-swagger' and commit the result:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi fi
.PHONY: swagger-validate .PHONY: swagger-validate
swagger-validate: ## check if the swagger spec is valid swagger-validate:
@# swagger "validate" requires that the "basePath" must start with a slash, but we are using Golang template "{{...}}" $(SED_INPLACE) '$(SWAGGER_SPEC_S_JSON)' './$(SWAGGER_SPEC)'
@$(SED_INPLACE) -E -e 's|"basePath":( *)"(.*)"|"basePath":\1"/\2"|g' './$(SWAGGER_SPEC)' # add a prefix slash to basePath
@# FIXME: there are some warnings
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)' $(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)'
.PHONY: checks .PHONY: checks
checks: checks-frontend checks-backend ## run various consistency checks checks: checks-frontend checks-backend
.PHONY: checks-frontend .PHONY: checks-frontend
checks-frontend: lockfile-check svg-check ## check frontend files checks-frontend: lockfile-check svg-check
.PHONY: checks-backend .PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check fix-check swagger-validate security-check ## check backend files checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint-spell ## lint everything lint: lint-frontend lint-backend lint-spell
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css ## lint frontend files lint-frontend: lint-js lint-css
.PHONY: lint-frontend-fix .PHONY: lint-frontend-fix
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues lint-frontend-fix: lint-js-fix lint-css-fix
.PHONY: lint-backend .PHONY: lint-backend
lint-backend: lint-go lint-go-gitea-vet lint-go-gopls lint-editorconfig ## lint backend files lint-backend: lint-go lint-go-vet lint-go-gopls lint-editorconfig
.PHONY: lint-backend-fix .PHONY: lint-backend-fix
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
.PHONY: lint-js .PHONY: lint-js
lint-js: node_modules ## lint js files lint-js: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
npx vue-tsc npx vue-tsc
.PHONY: lint-js-fix .PHONY: lint-js-fix
lint-js-fix: node_modules ## lint js files and fix issues lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
npx vue-tsc npx vue-tsc
.PHONY: lint-css .PHONY: lint-css
lint-css: node_modules ## lint css files lint-css: node_modules
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) npx stylelint --color --max-warnings=0 $(STYLELINT_FILES)
.PHONY: lint-css-fix .PHONY: lint-css-fix
lint-css-fix: node_modules ## lint css files and fix issues lint-css-fix: node_modules
npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix npx stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix
.PHONY: lint-swagger .PHONY: lint-swagger
lint-swagger: node_modules ## lint swagger files lint-swagger: node_modules
npx spectral lint -q -F hint $(SWAGGER_SPEC) npx spectral lint -q -F hint $(SWAGGER_SPEC)
.PHONY: lint-md .PHONY: lint-md
lint-md: node_modules ## lint markdown files lint-md: node_modules
npx markdownlint *.md npx markdownlint *.md
.PHONY: lint-spell .PHONY: lint-spell
lint-spell: ## lint spelling lint-spell:
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES) @go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
.PHONY: lint-spell-fix .PHONY: lint-spell-fix
lint-spell-fix: ## lint spelling and fix issues lint-spell-fix:
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES) @go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -w $(SPELLCHECK_FILES)
.PHONY: lint-go .PHONY: lint-go
lint-go: ## lint go files lint-go:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GO) run $(GOLANGCI_LINT_PACKAGE) run
.PHONY: lint-go-fix .PHONY: lint-go-fix
lint-go-fix: ## lint go files and fix issues lint-go-fix:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix $(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix
# workaround step for the lint-go-windows CI task because 'go run' can not # workaround step for the lint-go-windows CI task because 'go run' can not
@ -370,58 +423,57 @@ lint-go-windows:
@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
golangci-lint run golangci-lint run
.PHONY: lint-go-gitea-vet .PHONY: lint-go-vet
lint-go-gitea-vet: ## lint go files with gitea-vet lint-go-vet:
@echo "Running gitea-vet..." @echo "Running go vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet ./... @$(GO) vet -vettool=gitea-vet ./...
.PHONY: lint-go-gopls .PHONY: lint-go-gopls
lint-go-gopls: ## lint go files with gopls lint-go-gopls:
@echo "Running gopls check..." @echo "Running gopls check..."
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES) @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA)
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@echo "Running editorconfig check..."
@$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES) @$(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) $(EDITORCONFIG_FILES)
.PHONY: lint-actions .PHONY: lint-actions
lint-actions: ## lint action workflow files lint-actions:
$(GO) run $(ACTIONLINT_PACKAGE) $(GO) run $(ACTIONLINT_PACKAGE)
.PHONY: lint-templates .PHONY: lint-templates
lint-templates: .venv node_modules ## lint template files lint-templates: .venv node_modules
@node tools/lint-templates-svg.js @node tools/lint-templates-svg.js
@poetry run djlint $(shell find templates -type f -iname '*.tmpl') @poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml .PHONY: lint-yaml
lint-yaml: .venv ## lint yaml files lint-yaml: .venv
@poetry run yamllint -s . @poetry run yamllint .
.PHONY: watch .PHONY: watch
watch: ## watch everything and continuously rebuild watch:
@bash tools/watch.sh @bash tools/watch.sh
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: node-check node_modules ## watch frontend files and continuously rebuild watch-frontend: node-check node_modules
@rm -rf $(WEBPACK_DEST_ENTRIES) @rm -rf $(WEBPACK_DEST_ENTRIES)
NODE_ENV=development npx webpack --watch --progress NODE_ENV=development npx webpack --watch --progress
.PHONY: watch-backend .PHONY: watch-backend
watch-backend: go-check ## watch backend files and continuously rebuild watch-backend: go-check
GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml GITEA_RUN_MODE=dev $(GO) run $(AIR_PACKAGE) -c .air.toml
.PHONY: test .PHONY: test
test: test-frontend test-backend ## test everything test: test-frontend test-backend
.PHONY: test-backend .PHONY: test-backend
test-backend: ## test backend files test-backend:
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) @$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
.PHONY: test-frontend .PHONY: test-frontend
test-frontend: node_modules ## test frontend files test-frontend: node_modules
npx vitest npx vitest
.PHONY: test-check .PHONY: test-check
@ -430,7 +482,7 @@ test-check:
@diff=$$(git status -s); \ @diff=$$(git status -s); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "make test-backend has changed files in the source tree:"; \ echo "make test-backend has changed files in the source tree:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
echo "You should change the tests to create these files in a temporary directory."; \ echo "You should change the tests to create these files in a temporary directory."; \
echo "Do not simply add these files to .gitignore"; \ echo "Do not simply add these files to .gitignore"; \
exit 1; \ exit 1; \
@ -453,10 +505,10 @@ unit-test-coverage:
@$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 @$(GO) test $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: tidy .PHONY: tidy
tidy: ## run go mod tidy tidy:
$(eval MIN_GO_VERSION := $(shell grep -Eo '^go\s+[0-9]+\.[0-9.]+' go.mod | cut -d' ' -f2)) $(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) $(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 vendor: go.mod go.sum
$(GO) mod vendor $(GO) mod vendor
@ -467,17 +519,15 @@ tidy-check: tidy
@diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \ @diff=$$(git diff --color=always go.mod go.sum $(GO_LICENSE_FILE)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make tidy' and commit the result:"; \ echo "Please run 'make tidy' and commit the result:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi fi
.PHONY: go-licenses .PHONY: go-licenses
go-licenses: $(GO_LICENSE_FILE) ## regenerate go licenses go-licenses: $(GO_LICENSE_FILE)
$(GO_LICENSE_FILE): go.mod go.sum $(GO_LICENSE_FILE): go.mod go.sum
@rm -rf $(GO_LICENSE_FILE) -$(GO) run $(GO_LICENSES_PACKAGE) save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
$(GO) install $(GO_LICENSES_PACKAGE)
-GOOS=linux CGO_ENABLED=1 go-licenses save . --force --save_path=$(GO_LICENSE_TMP_DIR) 2>/dev/null
$(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE) $(GO) run build/generate-go-licenses.go $(GO_LICENSE_TMP_DIR) $(GO_LICENSE_FILE)
@rm -rf $(GO_LICENSE_TMP_DIR) @rm -rf $(GO_LICENSE_TMP_DIR)
@ -721,17 +771,17 @@ install: $(wildcard *.go)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
.PHONY: build .PHONY: build
build: frontend backend ## build everything build: frontend backend
.PHONY: frontend .PHONY: frontend
frontend: $(WEBPACK_DEST) ## build frontend files frontend: $(WEBPACK_DEST)
.PHONY: backend .PHONY: backend
backend: go-check generate-backend $(EXECUTABLE) ## build backend files backend: go-check generate-backend $(EXECUTABLE)
# We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend # We generate the backend before the frontend in case we in future we want to generate things in the frontend from generated files in backend
.PHONY: generate .PHONY: generate
generate: generate-backend ## run "go generate" generate: generate-backend
.PHONY: generate-backend .PHONY: generate-backend
generate-backend: $(TAGS_PREREQ) generate-go generate-backend: $(TAGS_PREREQ) generate-go
@ -743,7 +793,7 @@ generate-go: $(TAGS_PREREQ)
.PHONY: security-check .PHONY: security-check
security-check: security-check:
go run $(GOVULNCHECK_PACKAGE) -show color ./... go run $(GOVULNCHECK_PACKAGE) ./...
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ) $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@ CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
@ -796,20 +846,20 @@ release-sources: | $(DIST_DIRS)
rm -f $(STORED_VERSION_FILE) rm -f $(STORED_VERSION_FILE)
.PHONY: deps .PHONY: deps
deps: deps-frontend deps-backend deps-tools deps-py ## install dependencies deps: deps-frontend deps-backend deps-tools deps-py
.PHONY: deps-py .PHONY: deps-py
deps-py: .venv ## install python dependencies deps-py: .venv
.PHONY: deps-frontend .PHONY: deps-frontend
deps-frontend: node_modules ## install frontend dependencies deps-frontend: node_modules
.PHONY: deps-backend .PHONY: deps-backend
deps-backend: ## install backend dependencies deps-backend:
$(GO) mod download $(GO) mod download
.PHONY: deps-tools .PHONY: deps-tools
deps-tools: ## install tool dependencies deps-tools:
$(GO) install $(AIR_PACKAGE) & \ $(GO) install $(AIR_PACKAGE) & \
$(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \ $(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) & \
$(GO) install $(GOFUMPT_PACKAGE) & \ $(GO) install $(GOFUMPT_PACKAGE) & \
@ -822,7 +872,6 @@ deps-tools: ## install tool dependencies
$(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE) & \
$(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE) & \
$(GO) install $(GOPLS_PACKAGE) & \ $(GO) install $(GOPLS_PACKAGE) & \
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait wait
node_modules: package-lock.json node_modules: package-lock.json
@ -834,10 +883,10 @@ node_modules: package-lock.json
@touch .venv @touch .venv
.PHONY: update .PHONY: update
update: update-js update-py ## update js and py dependencies update: update-js update-py
.PHONY: update-js .PHONY: update-js
update-js: node-check | node_modules ## update js dependencies update-js: node-check | node_modules
npx updates -u -f package.json npx updates -u -f package.json
rm -rf node_modules package-lock.json rm -rf node_modules package-lock.json
npm install --package-lock npm install --package-lock
@ -846,14 +895,27 @@ update-js: node-check | node_modules ## update js dependencies
@touch node_modules @touch node_modules
.PHONY: update-py .PHONY: update-py
update-py: node-check | node_modules ## update py dependencies update-py: node-check | node_modules
npx updates -u -f pyproject.toml npx updates -u -f pyproject.toml
rm -rf .venv poetry.lock rm -rf .venv poetry.lock
poetry install poetry install
@touch .venv @touch .venv
.PHONY: fomantic
fomantic:
rm -rf $(FOMANTIC_WORK_DIR)/build
cd $(FOMANTIC_WORK_DIR) && npm install --no-save
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
$(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js
cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
.PHONY: webpack .PHONY: webpack
webpack: $(WEBPACK_DEST) ## build webpack files webpack: $(WEBPACK_DEST)
$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
@$(MAKE) -s node-check node_modules @$(MAKE) -s node-check node_modules
@ -863,7 +925,7 @@ $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json
@touch $(WEBPACK_DEST) @touch $(WEBPACK_DEST)
.PHONY: svg .PHONY: svg
svg: node-check | node_modules ## build svg files svg: node-check | node_modules
rm -rf $(SVG_DEST_DIR) rm -rf $(SVG_DEST_DIR)
node tools/generate-svg.js node tools/generate-svg.js
@ -873,7 +935,7 @@ svg-check: svg
@diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \ @diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \ echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@ -884,7 +946,7 @@ lockfile-check:
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "package-lock.json is inconsistent with package.json"; \ echo "package-lock.json is inconsistent with package.json"; \
echo "Please run 'npm install --package-lock-only' and commit the result:"; \ echo "Please run 'npm install --package-lock-only' and commit the result:"; \
printf "%s" "$${diff}"; \ echo "$${diff}"; \
exit 1; \ exit 1; \
fi fi
@ -898,8 +960,12 @@ update-translations:
mv ./translations/*.ini ./options/locale/ mv ./translations/*.ini ./options/locale/
rmdir ./translations rmdir ./translations
.PHONY: generate-license
generate-license:
$(GO) run build/generate-licenses.go
.PHONY: generate-gitignore .PHONY: generate-gitignore
generate-gitignore: ## update gitignore files generate-gitignore:
$(GO) run build/generate-gitignores.go $(GO) run build/generate-gitignores.go
.PHONY: generate-images .PHONY: generate-images
@ -908,7 +974,7 @@ generate-images: | node_modules
node tools/generate-images.js $(TAGS) node tools/generate-images.js $(TAGS)
.PHONY: generate-manpage .PHONY: generate-manpage
generate-manpage: ## generate manpage generate-manpage:
@[ -f gitea ] || make backend @[ -f gitea ] || make backend
@mkdir -p man/man1/ man/man5 @mkdir -p man/man1/ man/man5
@./gitea docs --man > man/man1/gitea.1 @./gitea docs --man > man/man1/gitea.1

108
README.md
View file

@ -9,9 +9,9 @@
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [![](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea "Crowdin")
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) [View this document in Chinese](./README_ZH.md)
## Purpose ## Purpose
@ -31,14 +31,6 @@ For accessing free Gitea service (with a limited number of repositories), you ca
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com). To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
## Documentation
You can find comprehensive documentation on our official [documentation website](https://docs.gitea.com/).
It includes installation, administration, usage, development, contributing guides, and more to help you get started and explore all features effectively.
If you have any suggestions or would like to contribute to it, you can visit the [documentation repository](https://gitea.com/gitea/docs)
## Building ## Building
From the root of the source tree, run: From the root of the source tree, run:
@ -60,8 +52,6 @@ More info: https://docs.gitea.com/installation/install-from-source
## Using ## Using
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
./gitea web ./gitea web
> [!NOTE] > [!NOTE]
@ -78,25 +68,22 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
## Translating ## Translating
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com) Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up. You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
Get more information from [documentation](https://docs.gitea.com/contributing/localization). https://docs.gitea.com/contributing/localization
## Official and Third-Party Projects [![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/act_runner) for Gitea Action. ## Further information
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more. For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.com/).
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
## Communication We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
If you have questions that are not covered by the [documentation](https://docs.gitea.com/), you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
## Authors ## Authors
@ -135,79 +122,18 @@ Gitea is pronounced [/ɡɪti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea"
We're [working on it](https://github.com/go-gitea/gitea/issues/1029). We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
**Where can I find the security patches?**
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
## License ## License
This project is licensed under the MIT License. This project is licensed under the MIT License.
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
for the full license text. for the full license text.
## Further information ## Screenshots
<details> Looking for an overview of the interface? Check it out!
<summary>Looking for an overview of the interface? Check it out!</summary>
### Login/Register Page |![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)|
|:---:|:---:|:---:|
![Login](https://dl.gitea.com/screenshots/login.png) |![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)|
![Register](https://dl.gitea.com/screenshots/register.png) |![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)|
|![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)|
### User Dashboard
![Home](https://dl.gitea.com/screenshots/home.png)
![Issues](https://dl.gitea.com/screenshots/issues.png)
![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png)
![Milestones](https://dl.gitea.com/screenshots/milestones.png)
### User Profile
![Profile](https://dl.gitea.com/screenshots/user_profile.png)
### Explore
![Repos](https://dl.gitea.com/screenshots/explore_repos.png)
![Users](https://dl.gitea.com/screenshots/explore_users.png)
![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png)
### Repository
![Home](https://dl.gitea.com/screenshots/repo_home.png)
![Commits](https://dl.gitea.com/screenshots/repo_commits.png)
![Branches](https://dl.gitea.com/screenshots/repo_branches.png)
![Labels](https://dl.gitea.com/screenshots/repo_labels.png)
![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png)
![Releases](https://dl.gitea.com/screenshots/repo_releases.png)
![Tags](https://dl.gitea.com/screenshots/repo_tags.png)
#### Repository Issue
![List](https://dl.gitea.com/screenshots/repo_issues.png)
![Issue](https://dl.gitea.com/screenshots/repo_issue.png)
#### Repository Pull Requests
![List](https://dl.gitea.com/screenshots/repo_pull_requests.png)
![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png)
![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png)
![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png)
#### Repository Actions
![List](https://dl.gitea.com/screenshots/repo_actions.png)
![Details](https://dl.gitea.com/screenshots/repo_actions_run.png)
#### Repository Activity
![Activity](https://dl.gitea.com/screenshots/repo_activity.png)
![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png)
![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png)
![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png)
### Organization
![Home](https://dl.gitea.com/screenshots/org_home.png)
</details>

View file

@ -1,206 +0,0 @@
# Gitea
[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [繁體中文](./README.zh-tw.md)
## 目的
这个项目的目标是提供最简单、最快速、最无痛的方式来设置自托管的 Git 服务。
由于 Gitea 是用 Go 语言编写的,它可以在 Go 支持的所有平台和架构上运行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架构。这个项目自 2016 年 11 月从 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而来,但已经有了很多变化。
在线演示可以访问 [demo.gitea.com](https://demo.gitea.com)。
要访问免费的 Gitea 服务(有一定数量的仓库限制),可以访问 [gitea.com](https://gitea.com/user/login)。
要快速部署您自己的专用 Gitea 实例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
## 文件
您可以在我们的官方 [文件网站](https://docs.gitea.com/) 上找到全面的文件。
它包括安装、管理、使用、开发、贡献指南等,帮助您快速入门并有效地探索所有功能。
如果您有任何建议或想要贡献,可以访问 [文件仓库](https://gitea.com/gitea/docs)
## 构建
从源代码树的根目录运行:
TAGS="bindata" make build
如果需要 SQLite 支持:
TAGS="bindata sqlite sqlite_unlock_notify" make build
`build` 目标分为两个子目标:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。
更多信息https://docs.gitea.com/installation/install-from-source
## 使用
构建后,默认情况下会在源代码树的根目录生成一个名为 `gitea` 的二进制文件。要运行它,请使用:
./gitea web
> [!注意]
> 如果您对使用我们的 API 感兴趣,我们提供了实验性支持,并附有 [文件](https://docs.gitea.com/api)。
## 贡献
预期的工作流程是Fork -> Patch -> Push -> Pull Request
> [!注意]
>
> 1. **在开始进行 Pull Request 之前,您必须阅读 [贡献者指南](CONTRIBUTING.md)。**
> 2. 如果您在项目中发现了漏洞,请私下写信给 **security@gitea.io**。谢谢!
## 翻译
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com)
翻译通过 [Crowdin](https://translate.gitea.com) 进行。如果您想翻译成新的语言,请在 Crowdin 项目中请求管理员添加新语言。
您也可以创建一个 issue 来添加语言,或者在 discord 的 #translation 频道上询问。如果您需要上下文或发现一些翻译问题,可以在字符串上留言或在 Discord 上询问。对于一般的翻译问题,文档中有一个部分。目前有点空,但我们希望随着问题的出现而填充它。
更多信息请参阅 [文件](https://docs.gitea.com/contributing/localization)。
## 官方和第三方项目
我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。
## 通讯
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
如果您有任何文件未涵盖的问题,可以在我们的 [Discord 服务器](https://discord.gg/Gitea) 上与我们联系,或者在 [discourse 论坛](https://forum.gitea.com/) 上创建帖子。
## 作者
- [维护者](https://github.com/orgs/go-gitea/people)
- [贡献者](https://github.com/go-gitea/gitea/graphs/contributors)
- [翻译者](options/locale/TRANSLATORS)
## 支持者
感谢所有支持者! 🙏 [[成为支持者](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## 赞助商
通过成为赞助商来支持这个项目。您的标志将显示在这里,并带有链接到您的网站。 [[成为赞助商](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
## 常见问题
**Gitea 怎么发音?**
Gitea 的发音是 [/ɡɪti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一样g 是硬音。
**为什么这个项目没有托管在 Gitea 实例上?**
我们正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
**在哪里可以找到安全补丁?**
在 [发布日志](https://github.com/go-gitea/gitea/releases) 或 [变更日志](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索关键词 `SECURITY` 以找到安全补丁。
## 许可证
这个项目是根据 MIT 许可证授权的。
请参阅 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以获取完整的许可证文本。
## 进一步信息
<details>
<summary>寻找界面概述?查看这里!</summary>
### 登录/注册页面
![Login](https://dl.gitea.com/screenshots/login.png)
![Register](https://dl.gitea.com/screenshots/register.png)
### 用户仪表板
![Home](https://dl.gitea.com/screenshots/home.png)
![Issues](https://dl.gitea.com/screenshots/issues.png)
![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png)
![Milestones](https://dl.gitea.com/screenshots/milestones.png)
### 用户资料
![Profile](https://dl.gitea.com/screenshots/user_profile.png)
### 探索
![Repos](https://dl.gitea.com/screenshots/explore_repos.png)
![Users](https://dl.gitea.com/screenshots/explore_users.png)
![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png)
### 仓库
![Home](https://dl.gitea.com/screenshots/repo_home.png)
![Commits](https://dl.gitea.com/screenshots/repo_commits.png)
![Branches](https://dl.gitea.com/screenshots/repo_branches.png)
![Labels](https://dl.gitea.com/screenshots/repo_labels.png)
![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png)
![Releases](https://dl.gitea.com/screenshots/repo_releases.png)
![Tags](https://dl.gitea.com/screenshots/repo_tags.png)
#### 仓库问题
![List](https://dl.gitea.com/screenshots/repo_issues.png)
![Issue](https://dl.gitea.com/screenshots/repo_issue.png)
#### 仓库拉取请求
![List](https://dl.gitea.com/screenshots/repo_pull_requests.png)
![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png)
![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png)
![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png)
#### 仓库操作
![List](https://dl.gitea.com/screenshots/repo_actions.png)
![Details](https://dl.gitea.com/screenshots/repo_actions_run.png)
#### 仓库活动
![Activity](https://dl.gitea.com/screenshots/repo_activity.png)
![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png)
![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png)
![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png)
### 组织
![Home](https://dl.gitea.com/screenshots/org_home.png)
</details>

View file

@ -1,206 +0,0 @@
# Gitea
[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin")
[English](./README.md) | [简体中文](./README.zh-cn.md)
## 目的
這個項目的目標是提供最簡單、最快速、最無痛的方式來設置自託管的 Git 服務。
由於 Gitea 是用 Go 語言編寫的,它可以在 Go 支援的所有平台和架構上運行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架構。這個項目自 2016 年 11 月從 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而來,但已經有了很多變化。
在線演示可以訪問 [demo.gitea.com](https://demo.gitea.com)。
要訪問免費的 Gitea 服務(有一定數量的倉庫限制),可以訪問 [gitea.com](https://gitea.com/user/login)。
要快速部署您自己的專用 Gitea 實例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 開始免費試用。
## 文件
您可以在我們的官方 [文件網站](https://docs.gitea.com/) 上找到全面的文件。
它包括安裝、管理、使用、開發、貢獻指南等,幫助您快速入門並有效地探索所有功能。
如果您有任何建議或想要貢獻,可以訪問 [文件倉庫](https://gitea.com/gitea/docs)
## 構建
從源代碼樹的根目錄運行:
TAGS="bindata" make build
如果需要 SQLite 支援:
TAGS="bindata sqlite sqlite_unlock_notify" make build
`build` 目標分為兩個子目標:
- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。
- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。
更多信息https://docs.gitea.com/installation/install-from-source
## 使用
構建後,默認情況下會在源代碼樹的根目錄生成一個名為 `gitea` 的二進制文件。要運行它,請使用:
./gitea web
> [!注意]
> 如果您對使用我們的 API 感興趣,我們提供了實驗性支援,並附有 [文件](https://docs.gitea.com/api)。
## 貢獻
預期的工作流程是Fork -> Patch -> Push -> Pull Request
> [!注意]
>
> 1. **在開始進行 Pull Request 之前,您必須閱讀 [貢獻者指南](CONTRIBUTING.md)。**
> 2. 如果您在項目中發現了漏洞,請私下寫信給 **security@gitea.io**。謝謝!
## 翻譯
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com)
翻譯通過 [Crowdin](https://translate.gitea.com) 進行。如果您想翻譯成新的語言,請在 Crowdin 項目中請求管理員添加新語言。
您也可以創建一個 issue 來添加語言,或者在 discord 的 #translation 頻道上詢問。如果您需要上下文或發現一些翻譯問題,可以在字符串上留言或在 Discord 上詢問。對於一般的翻譯問題,文檔中有一個部分。目前有點空,但我們希望隨著問題的出現而填充它。
更多信息請參閱 [文件](https://docs.gitea.com/contributing/localization)。
## 官方和第三方項目
我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。
## 通訊
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
如果您有任何文件未涵蓋的問題,可以在我們的 [Discord 服務器](https://discord.gg/Gitea) 上與我們聯繫,或者在 [discourse 論壇](https://forum.gitea.com/) 上創建帖子。
## 作者
- [維護者](https://github.com/orgs/go-gitea/people)
- [貢獻者](https://github.com/go-gitea/gitea/graphs/contributors)
- [翻譯者](options/locale/TRANSLATORS)
## 支持者
感謝所有支持者! 🙏 [[成為支持者](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## 贊助商
通過成為贊助商來支持這個項目。您的標誌將顯示在這裡,並帶有鏈接到您的網站。 [[成為贊助商](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
## 常見問題
**Gitea 怎麼發音?**
Gitea 的發音是 [/ɡɪti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一樣g 是硬音。
**為什麼這個項目沒有託管在 Gitea 實例上?**
我們正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。
**在哪裡可以找到安全補丁?**
在 [發佈日誌](https://github.com/go-gitea/gitea/releases) 或 [變更日誌](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索關鍵詞 `SECURITY` 以找到安全補丁。
## 許可證
這個項目是根據 MIT 許可證授權的。
請參閱 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以獲取完整的許可證文本。
## 進一步信息
<details>
<summary>尋找界面概述?查看這裡!</summary>
### 登錄/註冊頁面
![Login](https://dl.gitea.com/screenshots/login.png)
![Register](https://dl.gitea.com/screenshots/register.png)
### 用戶儀表板
![Home](https://dl.gitea.com/screenshots/home.png)
![Issues](https://dl.gitea.com/screenshots/issues.png)
![Pull Requests](https://dl.gitea.com/screenshots/pull_requests.png)
![Milestones](https://dl.gitea.com/screenshots/milestones.png)
### 用戶資料
![Profile](https://dl.gitea.com/screenshots/user_profile.png)
### 探索
![Repos](https://dl.gitea.com/screenshots/explore_repos.png)
![Users](https://dl.gitea.com/screenshots/explore_users.png)
![Orgs](https://dl.gitea.com/screenshots/explore_orgs.png)
### 倉庫
![Home](https://dl.gitea.com/screenshots/repo_home.png)
![Commits](https://dl.gitea.com/screenshots/repo_commits.png)
![Branches](https://dl.gitea.com/screenshots/repo_branches.png)
![Labels](https://dl.gitea.com/screenshots/repo_labels.png)
![Milestones](https://dl.gitea.com/screenshots/repo_milestones.png)
![Releases](https://dl.gitea.com/screenshots/repo_releases.png)
![Tags](https://dl.gitea.com/screenshots/repo_tags.png)
#### 倉庫問題
![List](https://dl.gitea.com/screenshots/repo_issues.png)
![Issue](https://dl.gitea.com/screenshots/repo_issue.png)
#### 倉庫拉取請求
![List](https://dl.gitea.com/screenshots/repo_pull_requests.png)
![Pull Request](https://dl.gitea.com/screenshots/repo_pull_request.png)
![File](https://dl.gitea.com/screenshots/repo_pull_request_file.png)
![Commits](https://dl.gitea.com/screenshots/repo_pull_request_commits.png)
#### 倉庫操作
![List](https://dl.gitea.com/screenshots/repo_actions.png)
![Details](https://dl.gitea.com/screenshots/repo_actions_run.png)
#### 倉庫活動
![Activity](https://dl.gitea.com/screenshots/repo_activity.png)
![Contributors](https://dl.gitea.com/screenshots/repo_contributors.png)
![Code Frequency](https://dl.gitea.com/screenshots/repo_code_frequency.png)
![Recent Commits](https://dl.gitea.com/screenshots/repo_recent_commits.png)
### 組織
![Home](https://dl.gitea.com/screenshots/org_home.png)
</details>

61
README_ZH.md Normal file
View file

@ -0,0 +1,61 @@
# Gitea
[![](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml/badge.svg?branch=main)](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly")
[![](https://img.shields.io/discord/322538954119184384.svg?logo=discord&logoColor=white&label=Discord&color=5865F2)](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card")
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc")
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release")
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source")
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT")
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea)
[![](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea "Crowdin")
[View this document in English](./README.md)
## 目标
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86amd64还包括 ARM 和 PowerPC。
如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。
如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。
如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
## 提示
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
3. 如果你要使用API请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
## 文档
关于如何安装请访问我们的 [文档站](https://docs.gitea.com/zh-cn/category/installation),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。
## 贡献流程
Fork -> Patch -> Push -> Pull Request
## 翻译
多语言翻译是基于Crowdin进行的.
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## 作者
* [Maintainers](https://github.com/orgs/go-gitea/people)
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
* [Translators](options/locale/TRANSLATORS)
## 授权许可
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。
## 截图
|![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)|
|:---:|:---:|:---:|
|![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)|
|![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)|
|![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)|

File diff suppressed because one or more lines are too long

View file

@ -5,10 +5,19 @@
package main package main
// Libraries that are included to vendor utilities used during Makefile build. // Libraries that are included to vendor utilities used during build.
// These libraries will not be included in a normal compilation. // These libraries will not be included in a normal compilation.
import ( import (
// for embed
_ "github.com/shurcooL/vfsgen"
// for cover merge
_ "golang.org/x/tools/cover"
// for vet // for vet
_ "code.gitea.io/gitea-vet" _ "code.gitea.io/gitea-vet"
// for swagger
_ "github.com/go-swagger/go-swagger/cmd/swagger"
) )

View file

@ -6,22 +6,87 @@
package main package main
import ( import (
"bytes"
"crypto/sha1"
"fmt" "fmt"
"log"
"net/http"
"os" "os"
"path/filepath"
"strconv"
"code.gitea.io/gitea/modules/assetfs" "github.com/shurcooL/vfsgen"
) )
func main() { func needsUpdate(dir, filename string) (bool, []byte) {
if len(os.Args) != 3 { needRegen := false
fmt.Println("usage: ./generate-bindata {local-directory} {bindata-filename}") _, err := os.Stat(filename)
os.Exit(1) if err != nil {
needRegen = true
} }
dir, filename := os.Args[1], os.Args[2] oldHash, err := os.ReadFile(filename + ".hash")
fmt.Printf("generating bindata for %s to %s\n", dir, filename) if err != nil {
if err := assetfs.GenerateEmbedBindata(dir, filename); err != nil { oldHash = []byte{}
fmt.Printf("failed: %s\n", err.Error())
os.Exit(1)
} }
hasher := sha1.New()
err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
_, _ = hasher.Write([]byte(d.Name()))
_, _ = hasher.Write([]byte(info.ModTime().String()))
_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
return nil
})
if err != nil {
return true, oldHash
}
newHash := hasher.Sum([]byte{})
if bytes.Compare(oldHash, newHash) != 0 {
return true, newHash
}
return needRegen, newHash
}
func main() {
if len(os.Args) < 4 {
log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
}
dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
var useGlobalModTime bool
if len(os.Args) == 5 {
useGlobalModTime, _ = strconv.ParseBool(os.Args[4])
}
update, newHash := needsUpdate(dir, filename)
if !update {
fmt.Printf("bindata for %s already up-to-date\n", packageName)
return
}
fmt.Printf("generating bindata for %s\n", packageName)
var fsTemplates http.FileSystem = http.Dir(dir)
err := vfsgen.Generate(fsTemplates, vfsgen.Options{
PackageName: packageName,
BuildTags: "bindata",
VariableName: "Assets",
Filename: filename,
UseGlobalModTime: useGlobalModTime,
})
if err != nil {
log.Fatalf("%v\n", err)
}
_ = os.WriteFile(filename+".hash", newHash, 0o666)
} }

176
build/generate-licenses.go Normal file
View file

@ -0,0 +1,176 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build ignore
package main
import (
"archive/tar"
"compress/gzip"
"crypto/md5"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/build/license"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
)
func main() {
var (
prefix = "gitea-licenses"
url = "https://api.github.com/repos/spdx/license-list-data/tarball"
githubApiToken = ""
githubUsername = ""
destination = ""
)
flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses")
flag.StringVar(&githubUsername, "username", "", "github username")
flag.StringVar(&githubApiToken, "token", "", "github api token")
flag.Parse()
file, err := os.CreateTemp(os.TempDir(), prefix)
if err != nil {
log.Fatalf("Failed to create temp file. %s", err)
}
defer util.Remove(file.Name())
if err := os.RemoveAll(destination); err != nil {
log.Fatalf("Cannot clean destination folder: %v", err)
}
if err := os.MkdirAll(destination, 0o755); err != nil {
log.Fatalf("Cannot create destination: %v", err)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatalf("Failed to download archive. %s", err)
}
if len(githubApiToken) > 0 && len(githubUsername) > 0 {
req.SetBasicAuth(githubUsername, githubApiToken)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Failed to download archive. %s", err)
}
defer resp.Body.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
log.Fatalf("Failed to copy archive to file. %s", err)
}
if _, err := file.Seek(0, 0); err != nil {
log.Fatalf("Failed to reset seek on archive. %s", err)
}
gz, err := gzip.NewReader(file)
if err != nil {
log.Fatalf("Failed to gunzip the archive. %s", err)
}
tr := tar.NewReader(gz)
aliasesFiles := make(map[string][]string)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("Failed to iterate archive. %s", err)
}
if !strings.Contains(hdr.Name, "/text/") {
continue
}
if filepath.Ext(hdr.Name) != ".txt" {
continue
}
fileBaseName := filepath.Base(hdr.Name)
licenseName := strings.TrimSuffix(fileBaseName, ".txt")
if strings.HasPrefix(fileBaseName, "README") {
continue
}
if strings.HasPrefix(fileBaseName, "deprecated_") {
continue
}
out, err := os.Create(path.Join(destination, licenseName))
if err != nil {
log.Fatalf("Failed to create new file. %s", err)
}
defer out.Close()
// some license files have same content, so we need to detect these files and create a convert map into a json file
// Later we use this convert map to avoid adding same license content with different license name
h := md5.New()
// calculate md5 and write file in the same time
r := io.TeeReader(tr, h)
if _, err := io.Copy(out, r); err != nil {
log.Fatalf("Failed to write new file. %s", err)
} else {
fmt.Printf("Written %s\n", out.Name())
md5 := hex.EncodeToString(h.Sum(nil))
aliasesFiles[md5] = append(aliasesFiles[md5], licenseName)
}
}
// generate convert license name map
licenseAliases := make(map[string]string)
for _, fileNames := range aliasesFiles {
if len(fileNames) > 1 {
licenseName := license.GetLicenseNameFromAliases(fileNames)
if licenseName == "" {
// license name should not be empty as expected
// if it is empty, we need to rewrite the logic of GetLicenseNameFromAliases
log.Fatalf("GetLicenseNameFromAliases: license name is empty")
}
for _, fileName := range fileNames {
licenseAliases[fileName] = licenseName
}
}
}
// save convert license name map to file
b, err := json.Marshal(licenseAliases)
if err != nil {
log.Fatalf("Failed to create json bytes. %s", err)
}
licenseAliasesDestination := filepath.Join(destination, "etc", "license-aliases.json")
if err := os.MkdirAll(filepath.Dir(licenseAliasesDestination), 0o755); err != nil {
log.Fatalf("Failed to create directory for license aliases json file. %s", err)
}
f, err := os.Create(licenseAliasesDestination)
if err != nil {
log.Fatalf("Failed to create license aliases json file. %s", err)
}
defer f.Close()
if _, err = f.Write(b); err != nil {
log.Fatalf("Failed to write license aliases json file. %s", err)
}
fmt.Println("Done")
}

View file

@ -0,0 +1,41 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package license
import "strings"
func GetLicenseNameFromAliases(fnl []string) string {
if len(fnl) == 0 {
return ""
}
shortestItem := func(list []string) string {
s := list[0]
for _, l := range list[1:] {
if len(l) < len(s) {
s = l
}
}
return s
}
allHasPrefix := func(list []string, s string) bool {
for _, l := range list {
if !strings.HasPrefix(l, s) {
return false
}
}
return true
}
sl := shortestItem(fnl)
slv := strings.Split(sl, "-")
var result string
for i := len(slv); i >= 0; i-- {
result = strings.Join(slv[:i], "-")
if allHasPrefix(fnl, result) {
return result
}
}
return ""
}

View file

@ -0,0 +1,39 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package license
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetLicenseNameFromAliases(t *testing.T) {
tests := []struct {
target string
inputs []string
}{
{
// real case which you can find in license-aliases.json
target: "AGPL-1.0",
inputs: []string{
"AGPL-1.0-only",
"AGPL-1.0-or-late",
},
},
{
target: "",
inputs: []string{
"APSL-1.0",
"AGPL-1.0-only",
"AGPL-1.0-or-late",
},
},
}
for _, tt := range tests {
result := GetLicenseNameFromAliases(tt.inputs)
assert.Equal(t, result, tt.target)
}
}

View file

@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -18,7 +17,7 @@ var (
CmdActions = &cli.Command{ CmdActions = &cli.Command{
Name: "actions", Name: "actions",
Usage: "Manage Gitea Actions", Usage: "Manage Gitea Actions",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdActionsGenRunnerToken, subcmdActionsGenRunnerToken,
}, },
} }
@ -39,7 +38,10 @@ var (
} }
) )
func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { func runGenerateActionsRunnerToken(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
scope := c.String("scope") scope := c.String("scope")

View file

@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -23,7 +23,7 @@ var (
CmdAdmin = &cli.Command{ CmdAdmin = &cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform common administrative operations", Usage: "Perform common administrative operations",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdUser, subcmdUser,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
@ -41,7 +41,7 @@ var (
subcmdRegenerate = &cli.Command{ subcmdRegenerate = &cli.Command{
Name: "regenerate", Name: "regenerate",
Usage: "Regenerate specific files", Usage: "Regenerate specific files",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdRegenHooks, microcmdRegenHooks,
microcmdRegenKeys, microcmdRegenKeys,
}, },
@ -50,15 +50,15 @@ var (
subcmdAuth = &cli.Command{ subcmdAuth = &cli.Command{
Name: "auth", Name: "auth",
Usage: "Modify external auth providers", Usage: "Modify external auth providers",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdAuthAddOauth(), microcmdAuthAddOauth,
microcmdAuthUpdateOauth(), microcmdAuthUpdateOauth,
microcmdAuthAddLdapBindDn(), microcmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn(), microcmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth(), microcmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP(), microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP(), microcmdAuthUpdateSMTP,
microcmdAuthList, microcmdAuthList,
microcmdAuthDelete, microcmdAuthDelete,
}, },
@ -70,9 +70,9 @@ var (
Action: runSendMail, Action: runSendMail,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "title", Name: "title",
Usage: "a title of a message", Usage: `a title of a message`,
Required: true, Value: "",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "content", Name: "content",
@ -86,16 +86,17 @@ var (
}, },
}, },
} }
)
func idFlag() *cli.Int64Flag { idFlag = &cli.Int64Flag{
return &cli.Int64Flag{
Name: "id", Name: "id",
Usage: "ID of authentication source", Usage: "ID of authentication source",
} }
} )
func runRepoSyncReleases(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
@ -106,7 +107,7 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
log.Trace("Synchronizing repository releases (this may take a while)") log.Trace("Synchronizing repository releases (this may take a while)")
for page := 1; ; page++ { for page := 1; ; page++ {
repos, count, err := repo_model.SearchRepositoryByName(ctx, repo_model.SearchRepoOptions{ repos, count, err := repo_model.SearchRepositoryByName(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
PageSize: repo_model.RepositoryListDefaultPageSize, PageSize: repo_model.RepositoryListDefaultPageSize,
Page: page, Page: page,

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -14,14 +13,14 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
auth_service "code.gitea.io/gitea/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
microcmdAuthDelete = &cli.Command{ microcmdAuthDelete = &cli.Command{
Name: "delete", Name: "delete",
Usage: "Delete specific auth source", Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()}, Flags: []cli.Flag{idFlag},
Action: runDeleteAuth, Action: runDeleteAuth,
} }
microcmdAuthList = &cli.Command{ microcmdAuthList = &cli.Command{
@ -57,7 +56,10 @@ var (
} }
) )
func runListAuth(ctx context.Context, c *cli.Command) error { func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
@ -88,11 +90,14 @@ func runListAuth(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
func runDeleteAuth(ctx context.Context, c *cli.Command) error { func runDeleteAuth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View file

@ -9,10 +9,9 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
type ( type (
@ -24,8 +23,8 @@ type (
} }
) )
func commonLdapCLIFlags() []cli.Flag { var (
return []cli.Flag{ commonLdapCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Usage: "Authentication name.", Usage: "Authentication name.",
@ -103,10 +102,8 @@ func commonLdapCLIFlags() []cli.Flag {
Usage: "The attribute of the users LDAP record containing the users avatar.", Usage: "The attribute of the users LDAP record containing the users avatar.",
}, },
} }
}
func ldapBindDnCLIFlags() []cli.Flag { ldapBindDnCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "bind-dn", Name: "bind-dn",
Usage: "The DN to bind to the LDAP server with when searching for the user.", Usage: "The DN to bind to the LDAP server with when searching for the user.",
@ -130,88 +127,50 @@ func ldapBindDnCLIFlags() []cli.Flag {
&cli.UintFlag{ &cli.UintFlag{
Name: "page-size", Name: "page-size",
Usage: "Search page size.", Usage: "Search page size.",
},
&cli.BoolFlag{
Name: "enable-groups",
Usage: "Enable LDAP groups",
},
&cli.StringFlag{
Name: "group-search-base-dn",
Usage: "The LDAP base DN at which group accounts will be searched for",
},
&cli.StringFlag{
Name: "group-member-attribute",
Usage: "Group attribute containing list of users",
},
&cli.StringFlag{
Name: "group-user-attribute",
Usage: "User attribute listed in group",
},
&cli.StringFlag{
Name: "group-filter",
Usage: "Verify group membership in LDAP",
},
&cli.StringFlag{
Name: "group-team-map",
Usage: "Map LDAP groups to Organization teams",
},
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
}) })
}
func ldapSimpleAuthCLIFlags() []cli.Flag { ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
return append(commonLdapCLIFlags(),
&cli.StringFlag{ &cli.StringFlag{
Name: "user-dn", Name: "user-dn",
Usage: "The user's DN.", Usage: "The user's DN.",
}) })
}
func microcmdAuthAddLdapBindDn() *cli.Command { microcmdAuthAddLdapBindDn = &cli.Command{
return &cli.Command{
Name: "add-ldap", Name: "add-ldap",
Usage: "Add new LDAP (via Bind DN) authentication source", Usage: "Add new LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapBindDn(ctx, cmd) return newAuthService().addLdapBindDn(c)
}, },
Flags: ldapBindDnCLIFlags(), Flags: ldapBindDnCLIFlags,
} }
}
func microcmdAuthUpdateLdapBindDn() *cli.Command { microcmdAuthUpdateLdapBindDn = &cli.Command{
return &cli.Command{
Name: "update-ldap", Name: "update-ldap",
Usage: "Update existing LDAP (via Bind DN) authentication source", Usage: "Update existing LDAP (via Bind DN) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapBindDn(ctx, cmd) return newAuthService().updateLdapBindDn(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
} }
}
func microcmdAuthAddLdapSimpleAuth() *cli.Command { microcmdAuthAddLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "add-ldap-simple", Name: "add-ldap-simple",
Usage: "Add new LDAP (simple auth) authentication source", Usage: "Add new LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().addLdapSimpleAuth(ctx, cmd) return newAuthService().addLdapSimpleAuth(c)
}, },
Flags: ldapSimpleAuthCLIFlags(), Flags: ldapSimpleAuthCLIFlags,
} }
}
func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { microcmdAuthUpdateLdapSimpleAuth = &cli.Command{
return &cli.Command{
Name: "update-ldap-simple", Name: "update-ldap-simple",
Usage: "Update existing LDAP (simple auth) authentication source", Usage: "Update existing LDAP (simple auth) authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(c *cli.Context) error {
return newAuthService().updateLdapSimpleAuth(ctx, cmd) return newAuthService().updateLdapSimpleAuth(c)
}, },
Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...), Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
} }
} )
// newAuthService creates a service with default functions. // newAuthService creates a service with default functions.
func newAuthService() *authService { func newAuthService() *authService {
@ -223,8 +182,8 @@ func newAuthService() *authService {
} }
} }
// parseAuthSourceLdap assigns values on authSource according to command line flags. // parseAuthSource assigns values on authSource according to command line flags.
func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) { func parseAuthSource(c *cli.Context, authSource *auth.Source) {
if c.IsSet("name") { if c.IsSet("name") {
authSource.Name = c.String("name") authSource.Name = c.String("name")
} }
@ -240,11 +199,10 @@ func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
if c.IsSet("disable-synchronize-users") { if c.IsSet("disable-synchronize-users") {
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users") authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
} }
authSource.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
} }
// parseLdapConfig assigns values on config according to command line flags. // parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Command, config *ldap.Source) error { func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("name") { if c.IsSet("name") {
config.Name = c.String("name") config.Name = c.String("name")
} }
@ -257,7 +215,7 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("security-protocol") { if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol")) p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok { if !ok {
return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol")) return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
} }
config.SecurityProtocol = p config.SecurityProtocol = p
} }
@ -312,26 +270,8 @@ func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
if c.IsSet("allow-deactivate-all") { if c.IsSet("allow-deactivate-all") {
config.AllowDeactivateAll = c.Bool("allow-deactivate-all") config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
} }
if c.IsSet("enable-groups") { if c.IsSet("skip-local-2fa") {
config.GroupsEnabled = c.Bool("enable-groups") config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
}
if c.IsSet("group-search-base-dn") {
config.GroupDN = c.String("group-search-base-dn")
}
if c.IsSet("group-member-attribute") {
config.GroupMemberUID = c.String("group-member-attribute")
}
if c.IsSet("group-user-attribute") {
config.UserUID = c.String("group-user-attribute")
}
if c.IsSet("group-filter") {
config.GroupFilter = c.String("group-filter")
}
if c.IsSet("group-team-map") {
config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
} }
return nil return nil
} }
@ -349,27 +289,32 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
// getAuthSource gets the login source by its id defined in the command line flags. // getAuthSource gets the login source by its id defined in the command line flags.
// It returns an error if the id is not set, does not match any source or if the source is not of expected type. // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) {
if err := argsSet(c, "id"); err != nil { if err := argsSet(c, "id"); err != nil {
return nil, err return nil, err
} }
authSource, err := a.getAuthSourceByID(ctx, c.Int64("id")) authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if authSource.Type != authType { if authSource.Type != authType {
return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
} }
return authSource, nil return authSource, nil
} }
// addLdapBindDn adds a new LDAP via Bind DN authentication source. // addLdapBindDn adds a new LDAP via Bind DN authentication source.
func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapBindDn(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@ -382,7 +327,7 @@ func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
}, },
} }
parseAuthSourceLdap(c, authSource) parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err return err
} }
@ -391,7 +336,10 @@ func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
} }
// updateLdapBindDn updates a new LDAP via Bind DN authentication source. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapBindDn(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@ -401,7 +349,7 @@ func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) erro
return err return err
} }
parseAuthSourceLdap(c, authSource) parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err return err
} }
@ -410,11 +358,14 @@ func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) erro
} }
// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
return err return err
} }
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@ -427,7 +378,7 @@ func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) err
}, },
} }
parseAuthSourceLdap(c, authSource) parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err return err
} }
@ -436,7 +387,10 @@ func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) err
} }
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := a.initDB(ctx); err != nil { if err := a.initDB(ctx); err != nil {
return err return err
} }
@ -446,7 +400,7 @@ func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command)
return err return err
} }
parseAuthSourceLdap(c, authSource) parseAuthSource(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err return err
} }

View file

@ -8,16 +8,17 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestAddLdapBindDn(t *testing.T) { func TestAddLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -50,13 +51,6 @@ func TestAddLdapBindDn(t *testing.T) {
"--attributes-in-bind", "--attributes-in-bind",
"--synchronize-users", "--synchronize-users",
"--page-size", "99", "--page-size", "99",
"--enable-groups",
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
"--group-member-attribute", "memberUid",
"--group-user-attribute", "uid",
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
"--group-team-map-removal",
}, },
source: &auth.Source{ source: &auth.Source{
Type: auth.LDAP, Type: auth.LDAP,
@ -84,13 +78,6 @@ func TestAddLdapBindDn(t *testing.T) {
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true, Enabled: true,
GroupsEnabled: true,
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
GroupMemberUID: "memberUid",
UserUID: "uid",
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
GroupTeamMapRemoval: true,
}, },
}, },
}, },
@ -134,7 +121,7 @@ func TestAddLdapBindDn(t *testing.T) {
"--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)",
"--email-attribute", "mail", "--email-attribute", "mail",
}, },
errMsg: "unknown security protocol name: zzzzz", errMsg: "Unknown security protocol name: zzzzz",
}, },
// case 3 // case 3
{ {
@ -228,23 +215,22 @@ func TestAddLdapBindDn(t *testing.T) {
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n) assert.FailNow(t, "case %d: should not call updateAuthSource", n)
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n) assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil
}, },
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthAddLdapBindDn().Flags, app.Flags = microcmdAuthAddLdapBindDn.Flags
Action: service.addLdapBindDn, app.Action = service.addLdapBindDn
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -256,7 +242,9 @@ func TestAddLdapBindDn(t *testing.T) {
func TestAddLdapSimpleAuth(t *testing.T) { func TestAddLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -346,12 +334,12 @@ func TestAddLdapSimpleAuth(t *testing.T) {
"--name", "ldap (simple auth) source", "--name", "ldap (simple auth) source",
"--security-protocol", "zzzzz", "--security-protocol", "zzzzz",
"--host", "ldap-server", "--host", "ldap-server",
"--port", "1234", "--port", "123",
"--user-filter", "(&(objectClass=posixAccount)(cn=%s))", "--user-filter", "(&(objectClass=posixAccount)(cn=%s))",
"--email-attribute", "mail", "--email-attribute", "mail",
"--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org",
}, },
errMsg: "unknown security protocol name: zzzzz", errMsg: "Unknown security protocol name: zzzzz",
}, },
// case 3 // case 3
{ {
@ -458,23 +446,22 @@ func TestAddLdapSimpleAuth(t *testing.T) {
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n) assert.FailNow(t, "case %d: should not call updateAuthSource", n)
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) {
assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n) assert.FailNow(t, "case %d: should not call getAuthSourceByID", n)
return nil, nil return nil, nil
}, },
} }
// Create a copy of command to test // Create a copy of command to test
app := &cli.Command{ app := cli.NewApp()
Flags: microcmdAuthAddLdapSimpleAuth().Flags, app.Flags = microcmdAuthAddLdapSimpleAuth.Flags
Action: service.addLdapSimpleAuth, app.Action = service.addLdapSimpleAuth
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -486,7 +473,9 @@ func TestAddLdapSimpleAuth(t *testing.T) {
func TestUpdateLdapBindDn(t *testing.T) { func TestUpdateLdapBindDn(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -521,13 +510,6 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--bind-password", "secret-bind-full", "--bind-password", "secret-bind-full",
"--synchronize-users", "--synchronize-users",
"--page-size", "99", "--page-size", "99",
"--enable-groups",
"--group-search-base-dn", "ou=group,dc=full-domain-bind,dc=org",
"--group-member-attribute", "memberUid",
"--group-user-attribute", "uid",
"--group-filter", "(|(cn=gitea_users)(cn=admins))",
"--group-team-map", `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
"--group-team-map-removal",
}, },
id: 23, id: 23,
existingAuthSource: &auth.Source{ existingAuthSource: &auth.Source{
@ -563,13 +545,6 @@ func TestUpdateLdapBindDn(t *testing.T) {
AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)",
RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)", RestrictedFilter: "(memberOf=cn=restricted-group,ou=example,dc=full-domain-bind,dc=org)",
Enabled: true, Enabled: true,
GroupsEnabled: true,
GroupDN: "ou=group,dc=full-domain-bind,dc=org",
GroupMemberUID: "memberUid",
UserUID: "uid",
GroupFilter: "(|(cn=gitea_users)(cn=admins))",
GroupTeamMap: `{"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}`,
GroupTeamMapRemoval: true,
}, },
}, },
}, },
@ -861,7 +836,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
"--id", "1", "--id", "1",
"--security-protocol", "xxxxx", "--security-protocol", "xxxxx",
}, },
errMsg: "unknown security protocol name: xxxxx", errMsg: "Unknown security protocol name: xxxxx",
}, },
// case 22 // case 22
{ {
@ -880,7 +855,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
Type: auth.OAuth2, Type: auth.OAuth2,
Cfg: &ldap.Source{}, Cfg: &ldap.Source{},
}, },
errMsg: "invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
}, },
// case 24 // case 24
{ {
@ -922,7 +897,7 @@ func TestUpdateLdapBindDn(t *testing.T) {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n) assert.FailNow(t, "case %d: should not call createAuthSource", n)
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
@ -944,12 +919,12 @@ func TestUpdateLdapBindDn(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthUpdateLdapBindDn().Flags, app.Flags = microcmdAuthUpdateLdapBindDn.Flags
Action: service.updateLdapBindDn, app.Action = service.updateLdapBindDn
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {
@ -961,7 +936,9 @@ func TestUpdateLdapBindDn(t *testing.T) {
func TestUpdateLdapSimpleAuth(t *testing.T) { func TestUpdateLdapSimpleAuth(t *testing.T) {
// Mock cli functions to do not exit on error // Mock cli functions to do not exit on error
defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() osExiter := cli.OsExiter
defer func() { cli.OsExiter = osExiter }()
cli.OsExiter = func(code int) {}
// Test cases // Test cases
cases := []struct { cases := []struct {
@ -1252,7 +1229,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
"--id", "1", "--id", "1",
"--security-protocol", "xxxxx", "--security-protocol", "xxxxx",
}, },
errMsg: "unknown security protocol name: xxxxx", errMsg: "Unknown security protocol name: xxxxx",
}, },
// case 18 // case 18
{ {
@ -1271,7 +1248,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
Type: auth.PAM, Type: auth.PAM,
Cfg: &ldap.Source{}, Cfg: &ldap.Source{},
}, },
errMsg: "invalid authentication type. expected: LDAP (simple auth), actual: PAM", errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
}, },
// case 20 // case 20
{ {
@ -1310,7 +1287,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(ctx context.Context, authSource *auth.Source) error {
assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n) assert.FailNow(t, "case %d: should not call createAuthSource", n)
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(ctx context.Context, authSource *auth.Source) error {
@ -1332,12 +1309,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
} }
// Create a copy of command to test // Create a copy of command to test
app := cli.Command{ app := cli.NewApp()
Flags: microcmdAuthUpdateLdapSimpleAuth().Flags, app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags
Action: service.updateLdapSimpleAuth, app.Action = service.updateLdapSimpleAuth
}
// Run it // Run it
err := app.Run(t.Context(), c.args) err := app.Run(c.args)
if c.errMsg != "" { if c.errMsg != "" {
assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) assert.EqualError(t, err, c.errMsg, "case %d: error should match", n)
} else { } else {

View file

@ -4,20 +4,18 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func oauthCLIFlags() []cli.Flag { var (
return []cli.Flag{ oauthCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@ -122,34 +120,23 @@ func oauthCLIFlags() []cli.Flag {
Usage: "Activate automatic team membership removal depending on groups", Usage: "Activate automatic team membership removal depending on groups",
}, },
} }
}
func microcmdAuthAddOauth() *cli.Command { microcmdAuthAddOauth = &cli.Command{
return &cli.Command{ Name: "add-oauth",
Name: "add-oauth", Usage: "Add new Oauth authentication source",
Usage: "Add new Oauth authentication source", Action: runAddOauth,
Action: func(ctx context.Context, cmd *cli.Command) error { Flags: oauthCLIFlags,
return newAuthService().runAddOauth(ctx, cmd)
},
Flags: oauthCLIFlags(),
} }
}
func microcmdAuthUpdateOauth() *cli.Command { microcmdAuthUpdateOauth = &cli.Command{
return &cli.Command{ Name: "update-oauth",
Name: "update-oauth", Usage: "Update existing Oauth authentication source",
Usage: "Update existing Oauth authentication source", Action: runUpdateOauth,
Action: func(ctx context.Context, cmd *cli.Command) error { Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
return newAuthService().runUpdateOauth(ctx, cmd)
},
Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, oauthCLIFlags()[1:]...)...),
} }
} )
func parseOAuth2Config(c *cli.Command) *oauth2.Source { func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") { if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{ customURLMapping = &oauth2.CustomURLMapping{
@ -169,6 +156,7 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source {
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"), OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping, CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"), IconURL: c.String("icon-url"),
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
Scopes: c.StringSlice("scopes"), Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"), RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"), RequiredClaimValue: c.String("required-claim-value"),
@ -180,8 +168,11 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source {
} }
} }
func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error { func runAddOauth(c *cli.Context) error {
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
@ -193,25 +184,27 @@ func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
} }
} }
return a.createAuthSource(ctx, &auth_model.Source{ return auth_model.CreateSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2, Type: auth_model.OAuth2,
Name: c.String("name"), Name: c.String("name"),
IsActive: true, IsActive: true,
Cfg: config, Cfg: config,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
}) })
} }
func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error { func runUpdateOauth(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := a.getAuthSourceByID(ctx, c.Int64("id")) source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
@ -301,6 +294,6 @@ func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error
oAuth2Config.CustomURLMapping = customURLMapping oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config source.Cfg = oAuth2Config
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source) return auth_model.UpdateSource(ctx, source)
} }

View file

@ -1,333 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestAddOauth(t *testing.T) {
testCases := []struct {
name string
args []string
source *auth_model.Source
errMsg string
}{
{
name: "valid config",
args: []string{
"--name", "test",
"--provider", "github",
"--key", "some_key",
"--secret", "some_secret",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "github",
ClientID: "some_key",
ClientSecret: "some_secret",
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with openid connect",
args: []string{
"--name", "test",
"--provider", "openidConnect",
"--key", "some_key",
"--secret", "some_secret",
"--auto-discover-url", "https://example.com",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Scopes: []string{},
Provider: "openidConnect",
ClientID: "some_key",
ClientSecret: "some_secret",
OpenIDConnectAutoDiscoveryURL: "https://example.com",
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
args: []string{
"--name", "test",
"--provider", "gitlab",
"--key", "some_key",
"--secret", "some_secret",
"--use-custom-urls", "true",
"--custom-token-url", "https://example.com/token",
"--custom-auth-url", "https://example.com/auth",
"--custom-profile-url", "https://example.com/profile",
"--custom-email-url", "https://example.com/email",
"--custom-tenant-id", "some_tenant",
"--icon-url", "https://example.com/icon",
"--scopes", "scope1,scope2",
"--skip-local-2fa", "true",
"--required-claim-name", "claim_name",
"--required-claim-value", "claim_value",
"--group-claim-name", "group_name",
"--admin-group", "admin",
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=true",
},
source: &auth_model.Source{
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "some_key",
ClientSecret: "some_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "some_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: true,
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var createdSource *auth_model.Source
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
createdSource = source
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthAddOauth().Flags,
Action: a.runAddOauth,
}
args := []string{"oauth-test"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
assert.Equal(t, tc.source, createdSource)
}
})
}
}
func TestUpdateOauth(t *testing.T) {
testCases := []struct {
name string
args []string
id int64
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
args: []string{
"--name", "test",
},
errMsg: "--id flag is missing",
},
{
name: "valid config",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "github",
ClientID: "old_key",
ClientSecret: "old_secret",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--provider", "gitlab",
"--key", "new_key",
"--secret", "new_secret",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "new_key",
ClientSecret: "new_secret",
CustomURLMapping: &oauth2.CustomURLMapping{},
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
id: 1,
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "old name",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "gitlab",
ClientID: "old_key",
ClientSecret: "old_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://old.example.com/token",
AuthURL: "https://old.example.com/auth",
ProfileURL: "https://old.example.com/profile",
EmailURL: "https://old.example.com/email",
Tenant: "old_tenant",
},
IconURL: "https://old.example.com/icon",
Scopes: []string{"old_scope1", "old_scope2"},
RequiredClaimName: "old_claim_name",
RequiredClaimValue: "old_claim_value",
GroupClaimName: "old_group_name",
AdminGroup: "old_admin",
RestrictedGroup: "old_restricted",
GroupTeamMap: `{"old_group1": [1,2]}`,
GroupTeamMapRemoval: true,
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--provider", "github",
"--key", "new_key",
"--secret", "new_secret",
"--use-custom-urls", "true",
"--custom-token-url", "https://example.com/token",
"--custom-auth-url", "https://example.com/auth",
"--custom-profile-url", "https://example.com/profile",
"--custom-email-url", "https://example.com/email",
"--custom-tenant-id", "new_tenant",
"--icon-url", "https://example.com/icon",
"--scopes", "scope1,scope2",
"--skip-local-2fa=true",
"--required-claim-name", "claim_name",
"--required-claim-value", "claim_value",
"--group-claim-name", "group_name",
"--admin-group", "admin",
"--restricted-group", "restricted",
"--group-team-map", `{"group1": [1,2]}`,
"--group-team-map-removal=false",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
Provider: "github",
ClientID: "new_key",
ClientSecret: "new_secret",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: "https://example.com/token",
AuthURL: "https://example.com/auth",
ProfileURL: "https://example.com/profile",
EmailURL: "https://example.com/email",
Tenant: "new_tenant",
},
IconURL: "https://example.com/icon",
Scopes: []string{"scope1", "scope2"},
RequiredClaimName: "claim_name",
RequiredClaimValue: "claim_value",
GroupClaimName: "group_name",
AdminGroup: "admin",
RestrictedGroup: "restricted",
GroupTeamMap: `{"group1": [1,2]}`,
GroupTeamMapRemoval: false,
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
return &auth_model.Source{
ID: 1,
Type: auth_model.OAuth2,
Name: "test",
IsActive: true,
Cfg: &oauth2.Source{
CustomURLMapping: &oauth2.CustomURLMapping{},
},
TwoFactorPolicy: "skip",
}, nil
},
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.authSource, source)
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthUpdateOauth().Flags,
Action: a.runUpdateOauth,
}
args := []string{"oauth-test"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}

View file

@ -1,271 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth/source/smtp"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestAddSMTP(t *testing.T) {
testCases := []struct {
name string
args []string
source *auth_model.Source
errMsg string
}{
{
name: "missing name",
args: []string{
"--host", "localhost",
"--port", "25",
},
errMsg: "name must be set",
},
{
name: "missing host",
args: []string{
"--name", "test",
"--port", "25",
},
errMsg: "host must be set",
},
{
name: "missing port",
args: []string{
"--name", "test",
"--host", "localhost",
},
errMsg: "port must be set",
},
{
name: "valid config",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
},
source: &auth_model.Source{
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "localhost",
Port: 25,
},
TwoFactorPolicy: "",
},
},
{
name: "valid config with options",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
"--auth-type", "LOGIN",
"--force-smtps",
"--skip-verify",
"--helo-hostname", "example.com",
"--disable-helo=true",
"--allowed-domains", "example.com,example.org",
"--skip-local-2fa",
"--active=false",
},
source: &auth_model.Source{
Type: auth_model.SMTP,
Name: "test",
IsActive: false,
Cfg: &smtp.Source{
Auth: "LOGIN",
Host: "localhost",
Port: 25,
ForceSMTPS: true,
SkipVerify: true,
HeloHostname: "example.com",
DisableHelo: true,
AllowedDomains: "example.com,example.org",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
createAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.source, source)
return nil
},
}
cmd := &cli.Command{
Flags: microcmdAuthAddSMTP().Flags,
Action: a.runAddSMTP,
}
args := []string{"smtp-test"}
args = append(args, tc.args...)
t.Log(args)
err := cmd.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUpdateSMTP(t *testing.T) {
testCases := []struct {
name string
args []string
existingAuthSource *auth_model.Source
authSource *auth_model.Source
errMsg string
}{
{
name: "missing id",
args: []string{
"--name", "test",
"--host", "localhost",
"--port", "25",
},
errMsg: "--id flag is missing",
},
{
name: "valid config",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
},
},
args: []string{
"--id", "1",
"--name", "test",
"--host", "localhost",
"--port", "25",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "localhost",
Port: 25,
},
},
},
{
name: "valid config with options",
existingAuthSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "old name",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
Host: "old host",
Port: 26,
HeloHostname: "old.example.com",
AllowedDomains: "old.example.com",
},
TwoFactorPolicy: "",
},
args: []string{
"--id", "1",
"--name", "test",
"--host", "localhost",
"--port", "25",
"--auth-type", "LOGIN",
"--force-smtps",
"--skip-verify",
"--helo-hostname", "example.com",
"--disable-helo",
"--allowed-domains", "example.com,example.org",
"--skip-local-2fa",
"--active=false",
},
authSource: &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: false,
Cfg: &smtp.Source{
Auth: "LOGIN",
Host: "localhost",
Port: 25,
ForceSMTPS: true,
SkipVerify: true,
HeloHostname: "example.com",
DisableHelo: true,
AllowedDomains: "example.com,example.org",
},
TwoFactorPolicy: "skip",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
a := &authService{
initDB: func(ctx context.Context) error {
return nil
},
getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) {
return &auth_model.Source{
ID: 1,
Type: auth_model.SMTP,
Name: "test",
IsActive: true,
Cfg: &smtp.Source{
Auth: "PLAIN",
},
}, nil
},
updateAuthSource: func(ctx context.Context, source *auth_model.Source) error {
assert.Equal(t, tc.authSource, source)
return nil
},
}
app := &cli.Command{
Flags: microcmdAuthUpdateSMTP().Flags,
Action: a.runUpdateSMTP,
}
args := []string{"smtp-tests"}
args = append(args, tc.args...)
err := app.Run(t.Context(), args)
if tc.errMsg != "" {
assert.EqualError(t, err, tc.errMsg)
} else {
assert.NoError(t, err)
}
})
}
}

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"strings" "strings"
@ -12,11 +11,11 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func smtpCLIFlags() []cli.Flag { var (
return []cli.Flag{ smtpCLIFlags = []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Value: "", Value: "",
@ -39,10 +38,12 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "force-smtps", Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.", Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
Value: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-verify", Name: "skip-verify",
Usage: "Skip TLS verify.", Usage: "Skip TLS verify.",
Value: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "helo-hostname", Name: "helo-hostname",
@ -52,6 +53,7 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "disable-helo", Name: "disable-helo",
Usage: "Disable SMTP helo.", Usage: "Disable SMTP helo.",
Value: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "allowed-domains", Name: "allowed-domains",
@ -61,6 +63,7 @@ func smtpCLIFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.", Usage: "Skip 2FA to log on.",
Value: true,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "active", Name: "active",
@ -68,34 +71,23 @@ func smtpCLIFlags() []cli.Flag {
Value: true, Value: true,
}, },
} }
}
func microcmdAuthUpdateSMTP() *cli.Command { microcmdAuthAddSMTP = &cli.Command{
return &cli.Command{ Name: "add-smtp",
Name: "update-smtp", Usage: "Add new SMTP authentication source",
Usage: "Update existing SMTP authentication source", Action: runAddSMTP,
Action: func(ctx context.Context, cmd *cli.Command) error { Flags: smtpCLIFlags,
return newAuthService().runUpdateSMTP(ctx, cmd)
},
Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}}, smtpCLIFlags()[1:]...)...),
} }
}
func microcmdAuthAddSMTP() *cli.Command { microcmdAuthUpdateSMTP = &cli.Command{
return &cli.Command{ Name: "update-smtp",
Name: "add-smtp", Usage: "Update existing SMTP authentication source",
Usage: "Add new SMTP authentication source", Action: runUpdateSMTP,
Action: func(ctx context.Context, cmd *cli.Command) error { Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
return newAuthService().runAddSMTP(ctx, cmd)
},
Flags: smtpCLIFlags(),
} }
} )
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("auth-type") { if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type") conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
@ -125,11 +117,17 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("disable-helo") { if c.IsSet("disable-helo") {
conf.DisableHelo = c.Bool("disable-helo") conf.DisableHelo = c.Bool("disable-helo")
} }
if c.IsSet("skip-local-2fa") {
conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
}
return nil return nil
} }
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error { func runAddSMTP(c *cli.Context) error {
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
@ -157,25 +155,27 @@ func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
smtpConfig.Auth = "PLAIN" smtpConfig.Auth = "PLAIN"
} }
return a.createAuthSource(ctx, &auth_model.Source{ return auth_model.CreateSource(ctx, &auth_model.Source{
Type: auth_model.SMTP, Type: auth_model.SMTP,
Name: c.String("name"), Name: c.String("name"),
IsActive: active, IsActive: active,
Cfg: &smtpConfig, Cfg: &smtpConfig,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
}) })
} }
func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error { func runUpdateSMTP(c *cli.Context) error {
if !c.IsSet("id") { if !c.IsSet("id") {
return errors.New("--id flag is missing") return errors.New("--id flag is missing")
} }
if err := a.initDB(ctx); err != nil { ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err return err
} }
source, err := a.getAuthSourceByID(ctx, c.Int64("id")) source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil { if err != nil {
return err return err
} }
@ -195,6 +195,6 @@ func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
} }
source.Cfg = smtpConfig source.Cfg = smtpConfig
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source) return auth_model.UpdateSource(ctx, source)
} }

View file

@ -4,13 +4,11 @@
package cmd package cmd
import ( import (
"context"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -27,14 +25,20 @@ var (
} }
) )
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
} }
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View file

@ -4,18 +4,18 @@
package cmd package cmd
import ( import (
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var subcmdUser = &cli.Command{ var subcmdUser = &cli.Command{
Name: "user", Name: "user",
Usage: "Modify users", Usage: "Modify users",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdUserCreate(), microcmdUserCreate,
microcmdUserList, microcmdUserList,
microcmdUserChangePassword(), microcmdUserChangePassword,
microcmdUserDelete(), microcmdUserDelete,
microcmdUserGenerateAccessToken, microcmdUserGenerateAccessToken,
microcmdUserMustChangePassword(), microcmdUserMustChangePassword,
}, },
} }

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@ -14,41 +13,44 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserChangePassword() *cli.Command { var microcmdUserChangePassword = &cli.Command{
return &cli.Command{ Name: "change-password",
Name: "change-password", Usage: "Change a user's password",
Usage: "Change a user's password", Action: runChangePassword,
Action: runChangePassword, Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.StringFlag{
&cli.StringFlag{ Name: "username",
Name: "username", Aliases: []string{"u"},
Aliases: []string{"u"}, Value: "",
Usage: "The user to change password for", Usage: "The user to change password for",
Required: true,
},
&cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Usage: "New password to set for user",
Required: true,
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password (can be disabled by --must-change-password=false)",
Value: true,
},
}, },
} &cli.StringFlag{
Name: "password",
Aliases: []string{"p"},
Value: "",
Usage: "New password to set for user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password (can be disabled by --must-change-password=false)",
Value: true,
},
},
} }
func runChangePassword(ctx context.Context, c *cli.Command) error { func runChangePassword(c *cli.Context) error {
if !setting.IsInTesting { if err := argsSet(c, "username", "password"); err != nil {
if err := initDB(ctx); err != nil { return err
return err }
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
} }
user, err := user_model.GetUserByName(ctx, c.String("username")) user, err := user_model.GetUserByName(ctx, c.String("username"))

View file

@ -1,91 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"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 TestChangePasswordCommand(t *testing.T) {
ctx := t.Context()
defer func() {
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
}()
t.Run("change password successfully", func(t *testing.T) {
// defer func() {
// require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
// }()
// Prepare test user
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
// load test user
userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
// Change the password
err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"})
require.NoError(t, err)
// Verify the password has been changed
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.NotEqual(t, userBase.Passwd, user.Passwd)
assert.NotEqual(t, userBase.Salt, user.Salt)
// Additional check for must-change-password flag
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"}))
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, user.MustChangePassword)
require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"}))
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, user.MustChangePassword)
})
t.Run("failure cases", func(t *testing.T) {
testCases := []struct {
name string
args []string
expectedErr string
}{
{
name: "user does not exist",
args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"},
expectedErr: "user does not exist",
},
{
name: "missing username",
args: []string{"change-password", "--password", "newpassword"},
expectedErr: `"username" not set`,
},
{
name: "missing password",
args: []string{"change-password", "--username", "testuser"},
expectedErr: `"password" not set`,
},
{
name: "too short password",
args: []string{"change-password", "--username", "testuser", "--password", "1"},
expectedErr: "password is not long enough",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := microcmdUserChangePassword().Run(ctx, tc.args)
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
})
}
})
}

View file

@ -16,110 +16,83 @@ import (
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserCreate() *cli.Command { var microcmdUserCreate = &cli.Command{
return &cli.Command{ Name: "create",
Name: "create", Usage: "Create a new user in database",
Usage: "Create a new user in database", Action: runCreateUser,
Action: runCreateUser, Flags: []cli.Flag{
MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ &cli.StringFlag{
{ Name: "name",
Flags: [][]cli.Flag{ Usage: "Username. DEPRECATED: use username instead",
{
&cli.StringFlag{
Name: "name",
Usage: "Username. DEPRECATED: use username instead",
},
&cli.StringFlag{
Name: "username",
Usage: "Username",
},
},
},
Required: true,
},
}, },
Flags: []cli.Flag{ &cli.StringFlag{
&cli.StringFlag{ Name: "username",
Name: "user-type", Usage: "Username",
Usage: "Set user's type: individual or bot",
Value: "individual",
},
&cli.StringFlag{
Name: "password",
Usage: "User password",
},
&cli.StringFlag{
Name: "email",
Usage: "User email address",
Required: true,
},
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
HideDefault: true,
},
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
&cli.BoolFlag{
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",
},
&cli.StringFlag{
Name: "fullname",
Usage: `The full, human-readable name of the user`,
},
}, },
} &cli.StringFlag{
Name: "password",
Usage: "User password",
},
&cli.StringFlag{
Name: "email",
Usage: "User email address",
},
&cli.BoolFlag{
Name: "admin",
Usage: "User is an admin",
},
&cli.BoolFlag{
Name: "random-password",
Usage: "Generate a random password for the user",
},
&cli.BoolFlag{
Name: "must-change-password",
Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)",
DisableDefaultText: true,
},
&cli.IntFlag{
Name: "random-password-length",
Usage: "Length of the random password to be generated",
Value: 12,
},
&cli.BoolFlag{
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",
},
},
} }
func runCreateUser(ctx context.Context, c *cli.Command) error { func runCreateUser(c *cli.Context) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first // this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings() setting.LoadSettings()
userTypes := map[string]user_model.UserType{ if err := argsSet(c, "email"); err != nil {
"individual": user_model.UserTypeIndividual, return err
"bot": user_model.UserTypeBot,
} }
userType, ok := userTypes[c.String("user-type")]
if !ok { if c.IsSet("name") && c.IsSet("username") {
return fmt.Errorf("invalid user type: %s", c.String("user-type")) return errors.New("cannot set both --name and --username flags")
} }
if userType != user_model.UserTypeIndividual { if !c.IsSet("name") && !c.IsSet("username") {
// Some other commands like "change-password" also only support individual users. return errors.New("one of --name or --username flags must be set")
// It needs to clarify the "password" behavior for bot users in the future.
// At the moment, we do not allow setting password for bot users.
if c.IsSet("password") || c.IsSet("random-password") {
return errors.New("password can only be set for individual users")
}
} }
if c.IsSet("password") && c.IsSet("random-password") { if c.IsSet("password") && c.IsSet("random-password") {
@ -131,12 +104,16 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
username = c.String("username") username = c.String("username")
} else { } else {
username = c.String("name") username = c.String("name")
_, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n") _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n")
} }
ctx := c.Context
if !setting.IsInTesting { if !setting.IsInTesting {
// FIXME: need to refactor the "initDB" related code later // FIXME: need to refactor the "installSignals/initDB" related code later
// it doesn't make sense to call it in (almost) every command action function // it doesn't make sense to call it in (almost) every command action function
var cancel context.CancelFunc
ctx, cancel = installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
@ -152,19 +129,16 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
return err return err
} }
fmt.Printf("generated random password is '%s'\n", password) fmt.Printf("generated random password is '%s'\n", password)
} else if userType == user_model.UserTypeIndividual { } else {
return errors.New("must set either password or random-password flag") return errors.New("must set either password or random-password flag")
} }
isAdmin := c.Bool("admin") isAdmin := c.Bool("admin")
mustChangePassword := true // always default to true mustChangePassword := true // always default to true
if c.IsSet("must-change-password") { if c.IsSet("must-change-password") {
if userType != user_model.UserTypeIndividual {
return errors.New("must-change-password flag can only be set for individual users")
}
// if the flag is set, use the value provided by the user // if the flag is set, use the value provided by the user
mustChangePassword = c.Bool("must-change-password") mustChangePassword = c.Bool("must-change-password")
} else if userType == user_model.UserTypeIndividual { } else {
// check whether there are users in the database // check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{}) hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil { if err != nil {
@ -188,12 +162,10 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
u := &user_model.User{ u := &user_model.User{
Name: username, Name: username,
Email: c.String("email"), Email: c.String("email"),
IsAdmin: isAdmin,
Type: userType,
Passwd: password, Passwd: password,
IsAdmin: isAdmin,
MustChangePassword: mustChangePassword, MustChangePassword: mustChangePassword,
Visibility: visibility, Visibility: visibility,
FullName: c.String("fullname"),
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
@ -226,8 +198,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err) return fmt.Errorf("CreateUser: %w", err)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
// create the access token // create the access token
if accessTokenScope != "" { if accessTokenScope != "" {
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
@ -236,5 +206,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error {
} }
fmt.Printf("Access token was successfully created... %s\n", t.Token) fmt.Printf("Access token was successfully created... %s\n", t.Token)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil return nil
} }

View file

@ -18,22 +18,19 @@ import (
) )
func TestAdminUserCreate(t *testing.T) { func TestAdminUserCreate(t *testing.T) {
app := NewMainApp(AppVersion{})
reset := func() { reset := func() {
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) 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, &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
} }
t.Run("MustChangePassword", func(t *testing.T) { t.Run("MustChangePassword", func(t *testing.T) {
type check struct { type check struct{ IsAdmin, MustChangePassword bool }
IsAdmin bool
MustChangePassword bool
}
createCheck := func(name, args string) check { createCheck := func(name, args string) check {
require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) 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}) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} return check{u.IsAdmin, u.MustChangePassword}
} }
reset() reset()
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("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")
@ -49,22 +46,10 @@ func TestAdminUserCreate(t *testing.T) {
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false")) assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
}) })
createUser := func(name string, args ...string) error { createUser := func(name, args string) error {
return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...)) return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
} }
t.Run("UserType", func(t *testing.T) {
reset()
assert.ErrorContains(t, createUser("u", "--user-type", "invalid"), "invalid user type")
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--password", "123"), "can only be set for individual users")
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--must-change-password"), "can only be set for individual users")
assert.NoError(t, createUser("u", "--user-type", "bot"))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
assert.Equal(t, user_model.UserTypeBot, u.Type)
assert.Empty(t, u.Passwd)
})
t.Run("AccessToken", func(t *testing.T) { t.Run("AccessToken", func(t *testing.T) {
// no generated access token // no generated access token
reset() reset()
@ -74,7 +59,7 @@ func TestAdminUserCreate(t *testing.T) {
// using "--access-token" only means "all" access // using "--access-token" only means "all" access
reset() reset()
assert.NoError(t, createUser("u", "--random-password", "--access-token")) 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, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
@ -84,7 +69,7 @@ func TestAdminUserCreate(t *testing.T) {
// using "--access-token" with name & scopes // using "--access-token" with name & scopes
reset() reset()
assert.NoError(t, createUser("u", "--random-password", "--access-token", "--access-token-name", "new-token-name", "--access-token-scopes", "read:issue,read:user")) 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, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
@ -97,38 +82,23 @@ func TestAdminUserCreate(t *testing.T) {
// using "--access-token-name" without "--access-token" // using "--access-token-name" without "--access-token"
reset() reset()
err = createUser("u", "--random-password", "--access-token-name", "new-token-name") 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, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) 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") 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" // using "--access-token-scopes" without "--access-token"
reset() reset()
err = createUser("u", "--random-password", "--access-token-scopes", "read:issue") 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, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) 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") assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
// empty permission // empty permission
reset() reset()
err = createUser("u", "--random-password", "--access-token", "--access-token-scopes", "public-only") 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, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access token does not have any permission") assert.ErrorContains(t, err, "access token does not have any permission")
}) })
t.Run("UserFields", func(t *testing.T) {
reset()
assert.NoError(t, createUser("u-FullNameWithSpace", "--random-password", "--fullname", "First O'Middle Last"))
unittest.AssertExistsAndLoadBean(t, &user_model.User{
Name: "u-FullNameWithSpace",
LowerName: "u-fullnamewithspace",
FullName: "First O'Middle Last",
Email: "u-FullNameWithSpace@gitea.local",
})
assert.NoError(t, createUser("u-FullNameEmpty", "--random-password", "--fullname", ""))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u-fullnameempty"})
assert.Empty(t, u.FullName)
})
} }

View file

@ -4,56 +4,53 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserDelete() *cli.Command { var microcmdUserDelete = &cli.Command{
return &cli.Command{ Name: "delete",
Name: "delete", Usage: "Delete specific user by id, name or email",
Usage: "Delete specific user by id, name or email", Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.Int64Flag{
&cli.Int64Flag{ Name: "id",
Name: "id", Usage: "ID of user of the user to delete",
Usage: "ID of user of the user to delete",
},
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
}, },
Action: runDeleteUser, &cli.StringFlag{
} Name: "username",
Aliases: []string{"u"},
Usage: "Username of the user to delete",
},
&cli.StringFlag{
Name: "email",
Aliases: []string{"e"},
Usage: "Email of the user to delete",
},
&cli.BoolFlag{
Name: "purge",
Usage: "Purge user, all their repositories, organizations and comments",
},
},
Action: runDeleteUser,
} }
func runDeleteUser(ctx context.Context, c *cli.Command) error { func runDeleteUser(c *cli.Context) error {
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
return errors.New("You must provide the id, username or email of a user to delete") return errors.New("You must provide the id, username or email of a user to delete")
} }
if !setting.IsInTesting { ctx, cancel := installSignals()
if err := initDB(ctx); err != nil { defer cancel()
return err
} if err := initDB(ctx); err != nil {
return err
} }
if err := storage.Init(); err != nil { if err := storage.Init(); err != nil {
@ -73,11 +70,11 @@ func runDeleteUser(ctx context.Context, c *cli.Command) error {
return err return err
} }
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) { if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
return fmt.Errorf("the user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username")) return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
} }
if c.IsSet("id") && user.ID != c.Int64("id") { if c.IsSet("id") && user.ID != c.Int64("id") {
return fmt.Errorf("the user %s does not match the provided id %d", user.Name, c.Int64("id")) return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
} }
return user_service.DeleteUser(ctx, user, c.Bool("purge")) return user_service.DeleteUser(ctx, user, c.Bool("purge"))

View file

@ -1,111 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"strconv"
"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/require"
)
func TestAdminUserDelete(t *testing.T) {
ctx := t.Context()
defer func() {
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{}))
}()
setupTestUser := func(t *testing.T) {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
}
t.Run("delete user by id", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by username", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by email", func(t *testing.T) {
setupTestUser(t)
err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
t.Run("delete user by all 3 attributes", func(t *testing.T) {
setupTestUser(t)
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"})
require.NoError(t, err)
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
})
}
func TestAdminUserDeleteFailure(t *testing.T) {
testCases := []struct {
name string
args []string
expectedErr string
}{
{
name: "no user to delete",
args: []string{"delete", "--username", "nonexistentuser"},
expectedErr: "user does not exist",
},
{
name: "user exists but provided username does not match",
args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"},
expectedErr: "the user testuser who has email testuser@gitea.local does not match the provided username wrongusername",
},
{
name: "user exists but provided id does not match",
args: []string{"delete", "--username", "testuser", "--id", "999"},
expectedErr: "the user testuser does not match the provided id 999",
},
{
name: "no required flags are provided",
args: []string{"delete"},
expectedErr: "You must provide the id, username or email of a user to delete",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := t.Context()
if strings.Contains(tc.name, "user exists") {
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"})
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
}
err := microcmdUserDelete().Run(ctx, tc.args)
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
})
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{}))
}
}

View file

@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var microcmdUserGenerateAccessToken = &cli.Command{ var microcmdUserGenerateAccessToken = &cli.Command{
@ -42,11 +41,14 @@ var microcmdUserGenerateAccessToken = &cli.Command{
Action: runGenerateAccessToken, Action: runGenerateAccessToken,
} }
func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { func runGenerateAccessToken(c *cli.Context) error {
if !c.IsSet("username") { 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()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View file

@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"text/tabwriter" "text/tabwriter"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var microcmdUserList = &cli.Command{ var microcmdUserList = &cli.Command{
@ -26,7 +25,10 @@ var microcmdUserList = &cli.Command{
}, },
} }
func runListUsers(ctx context.Context, c *cli.Command) error { func runListUsers(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }

View file

@ -4,41 +4,40 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func microcmdUserMustChangePassword() *cli.Command { var microcmdUserMustChangePassword = &cli.Command{
return &cli.Command{ Name: "must-change-password",
Name: "must-change-password", Usage: "Set the must change password flag for the provided users or all users",
Usage: "Set the must change password flag for the provided users or all users", Action: runMustChangePassword,
Action: runMustChangePassword, Flags: []cli.Flag{
Flags: []cli.Flag{ &cli.BoolFlag{
&cli.BoolFlag{ Name: "all",
Name: "all", Aliases: []string{"A"},
Aliases: []string{"A"}, Usage: "All users must change password, except those explicitly excluded with --exclude",
Usage: "All users must change password, except those explicitly excluded with --exclude",
},
&cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
}, },
} &cli.StringSliceFlag{
Name: "exclude",
Aliases: []string{"e"},
Usage: "Do not change the must-change-password flag for these users",
},
&cli.BoolFlag{
Name: "unset",
Usage: "Instead of setting the must-change-password flag, unset it",
},
},
} }
func runMustChangePassword(ctx context.Context, c *cli.Command) error { func runMustChangePassword(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if c.NArg() == 0 && !c.IsSet("all") { if c.NArg() == 0 && !c.IsSet("all") {
return errors.New("either usernames or --all must be provided") return errors.New("either usernames or --all must be provided")
} }
@ -47,10 +46,8 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error {
all := c.Bool("all") all := c.Bool("all")
exclude := c.StringSlice("exclude") exclude := c.StringSlice("exclude")
if !setting.IsInTesting { if err := initDB(ctx); err != nil {
if err := initDB(ctx); err != nil { return err
return err
}
} }
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude) n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude)

View file

@ -1,78 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"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 TestMustChangePassword(t *testing.T) {
defer func() {
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
}()
err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"})
require.NoError(t, err)
err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"})
require.NoError(t, err)
// Reset password change flag
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"})
require.NoError(t, err)
testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Make all users change password
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag but exclude all tested users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.True(t, testUserExclude.MustChangePassword)
// Reset password change flag by listing multiple users
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Exclude a user from all user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.True(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
// Unset a flag for single user
err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"})
require.NoError(t, err)
testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"})
assert.False(t, testUser.MustChangePassword)
testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"})
assert.False(t, testUserExclude.MustChangePassword)
}

View file

@ -6,7 +6,6 @@
package cmd package cmd
import ( import (
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
@ -14,7 +13,6 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt"
"log" "log"
"math/big" "math/big"
"net" "net"
@ -22,59 +20,47 @@ import (
"strings" "strings"
"time" "time"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdCert represents the available cert sub-command. // CmdCert represents the available cert sub-command.
func cmdCert() *cli.Command { var CmdCert = &cli.Command{
return &cli.Command{ Name: "cert",
Name: "cert", Usage: "Generate self-signed certificate",
Usage: "Generate self-signed certificate", Description: `Generate a self-signed X.509 certificate for a TLS server.
Description: `Generate a self-signed X.509 certificate for a TLS server.
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
Action: runCert, Action: runCert,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "host", Name: "host",
Usage: "Comma-separated hostnames and IPs to generate a certificate for", Value: "",
Required: true, Usage: "Comma-separated hostnames and IPs to generate a certificate for",
},
&cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
&cli.StringFlag{
Name: "out",
Value: "cert.pem",
Usage: "Path to the file where there certificate will be saved",
},
&cli.StringFlag{
Name: "keyout",
Value: "key.pem",
Usage: "Path to the file where there certificate key will be saved",
},
}, },
} &cli.StringFlag{
Name: "ecdsa-curve",
Value: "",
Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521",
},
&cli.IntFlag{
Name: "rsa-bits",
Value: 3072,
Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set",
},
&cli.StringFlag{
Name: "start-date",
Value: "",
Usage: "Creation date formatted as Jan 1 15:04:05 2011",
},
&cli.DurationFlag{
Name: "duration",
Value: 365 * 24 * time.Hour,
Usage: "Duration that certificate is valid for",
},
&cli.BoolFlag{
Name: "ca",
Usage: "whether this cert should be its own Certificate Authority",
},
},
} }
func publicKey(priv any) any { func publicKey(priv any) any {
@ -103,7 +89,11 @@ func pemBlockForKey(priv any) *pem.Block {
} }
} }
func runCert(_ context.Context, c *cli.Command) error { func runCert(c *cli.Context) error {
if err := argsSet(c, "host"); err != nil {
return err
}
var priv any var priv any
var err error var err error
switch c.String("ecdsa-curve") { switch c.String("ecdsa-curve") {
@ -118,17 +108,17 @@ func runCert(_ context.Context, c *cli.Command) error {
case "P521": case "P521":
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default: default:
err = fmt.Errorf("unrecognized elliptic curve: %q", c.String("ecdsa-curve")) log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve"))
} }
if err != nil { if err != nil {
return fmt.Errorf("failed to generate private key: %w", err) log.Fatalf("Failed to generate private key: %v", err)
} }
var notBefore time.Time var notBefore time.Time
if startDate := c.String("start-date"); startDate != "" { if startDate := c.String("start-date"); startDate != "" {
notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate) notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse creation date %w", err) log.Fatalf("Failed to parse creation date: %v", err)
} }
} else { } else {
notBefore = time.Now() notBefore = time.Now()
@ -139,7 +129,7 @@ func runCert(_ context.Context, c *cli.Command) error {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate serial number: %w", err) log.Fatalf("Failed to generate serial number: %v", err)
} }
template := x509.Certificate{ template := x509.Certificate{
@ -156,8 +146,8 @@ func runCert(_ context.Context, c *cli.Command) error {
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
hosts := strings.SplitSeq(c.String("host"), ",") hosts := strings.Split(c.String("host"), ",")
for h := range hosts { for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil { if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip) template.IPAddresses = append(template.IPAddresses, ip)
} else { } else {
@ -172,35 +162,35 @@ func runCert(_ context.Context, c *cli.Command) error {
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil { if err != nil {
return fmt.Errorf("failed to create certificate: %w", err) log.Fatalf("Failed to create certificate: %v", err)
} }
certOut, err := os.Create(c.String("out")) certOut, err := os.Create("cert.pem")
if err != nil { if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) log.Fatalf("Failed to open cert.pem for writing: %v", err)
} }
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil { if err != nil {
return fmt.Errorf("failed to encode certificate: %w", err) log.Fatalf("Failed to encode certificate: %v", err)
} }
err = certOut.Close() err = certOut.Close()
if err != nil { if err != nil {
return fmt.Errorf("failed to write cert: %w", err) log.Fatalf("Failed to write cert: %v", err)
} }
fmt.Fprintf(c.Writer, "Written cert to %s\n", c.String("out")) log.Println("Written cert.pem")
keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil { if err != nil {
return fmt.Errorf("failed to open %s for writing: %w", c.String("keyout"), err) log.Fatalf("Failed to open key.pem for writing: %v", err)
} }
err = pem.Encode(keyOut, pemBlockForKey(priv)) err = pem.Encode(keyOut, pemBlockForKey(priv))
if err != nil { if err != nil {
return fmt.Errorf("failed to encode key: %w", err) log.Fatalf("Failed to encode key: %v", err)
} }
err = keyOut.Close() err = keyOut.Close()
if err != nil { if err != nil {
return fmt.Errorf("failed to write key: %w", err) log.Fatalf("Failed to write key: %v", err)
} }
fmt.Fprintf(c.Writer, "Written key to %s\n", c.String("keyout")) log.Println("Written key.pem")
return nil return nil
} }

View file

@ -1,123 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCertCommand(t *testing.T) {
cases := []struct {
name string
args []string
}{
{
name: "RSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "2048",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "ECDSA cert generation",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "P256",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
{
name: "mixed host, certificate authority",
args: []string{
"cert-test",
"--host", "localhost,127.0.0.1",
"--duration", "1h",
"--start-date", "Jan 1 00:00:00 2024",
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := cmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
require.NoError(t, err)
assert.FileExists(t, certFile)
assert.FileExists(t, keyFile)
})
}
}
func TestCertCommandFailures(t *testing.T) {
cases := []struct {
name string
args []string
errMsg string
}{
{
name: "Start Date Parsing failure",
args: []string{
"cert-test",
"--host", "localhost",
"--start-date", "invalid-date",
},
errMsg: "parsing time",
},
{
name: "Unknown curve",
args: []string{
"cert-test",
"--host", "localhost",
"--ecdsa-curve", "invalid-curve",
},
errMsg: "unrecognized elliptic curve",
},
{
name: "Key generation failure",
args: []string{
"cert-test",
"--host", "localhost",
"--rsa-bits", "invalid-bits",
},
},
{
name: "Missing parameters",
args: []string{
"cert-test",
},
errMsg: `"host" not set`,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
app := cmdCert()
tempDir := t.TempDir()
certFile := filepath.Join(tempDir, "cert.pem")
keyFile := filepath.Join(tempDir, "key.pem")
err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile))
require.Error(t, err)
if c.errMsg != "" {
assert.ErrorContains(t, err, c.errMsg)
}
assert.NoFileExists(t, certFile)
assert.NoFileExists(t, keyFile)
})
}
}

View file

@ -18,19 +18,20 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// argsSet checks that all the required arguments are set. args is a list of // argsSet checks that all the required arguments are set. args is a list of
// arguments that must be set in the passed Context. // arguments that must be set in the passed Context.
func argsSet(c *cli.Command, args ...string) error { func argsSet(c *cli.Context, args ...string) error {
for _, a := range args { for _, a := range args {
if !c.IsSet(a) { if !c.IsSet(a) {
return errors.New(a + " is not set") return errors.New(a + " is not set")
} }
if c.Value(a) == nil { if util.IsEmptyString(c.String(a)) {
return errors.New(a + " is required") return errors.New(a + " is required")
} }
} }
@ -108,7 +109,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
} }
func globalBool(c *cli.Command, name string) bool { func globalBool(c *cli.Context, name string) bool {
for _, ctx := range c.Lineage() { for _, ctx := range c.Lineage() {
if ctx.Bool(name) { if ctx.Bool(name) {
return true return true
@ -119,8 +120,8 @@ func globalBool(c *cli.Command, name string) bool {
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) { func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(ctx context.Context, c *cli.Command) (context.Context, error) { return func(c *cli.Context) error {
level := defaultLevel level := defaultLevel
if globalBool(c, "quiet") { if globalBool(c, "quiet") {
level = log.FATAL level = log.FATAL
@ -129,16 +130,6 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cl
level = log.TRACE level = log.TRACE
} }
log.SetConsoleLogger(log.DEFAULT, "console-default", level) log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return ctx, nil return nil
} }
} }
func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
// Dirty patch for urfave/cli's strange design.
// "./gitea bad-cmd" should not start the web server.
rootArgs := cmd.Root().Args().Slice()
if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
return rootArgs[0], false
}
return "", true
}

View file

@ -1,38 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestDefaultCommand(t *testing.T) {
test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
called := false
cmd := &cli.Command{
DefaultCommand: "test",
Commands: []*cli.Command{
{
Name: "test",
Action: func(ctx context.Context, command *cli.Command) error {
retName, retValid := isValidDefaultSubCommand(command)
assert.Equal(t, expectedRetName, retName)
assert.Equal(t, expectedRetValid, retValid)
called = true
return nil
},
},
},
}
assert.NoError(t, cmd.Run(t.Context(), args))
assert.True(t, called)
}
test(t, []string{"./gitea"}, "", true)
test(t, []string{"./gitea", "test"}, "", true)
test(t, []string{"./gitea", "other"}, "other", false)
}

View file

@ -4,13 +4,11 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
cli_docs "github.com/urfave/cli-docs/v3" "github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
) )
// CmdDocs represents the available docs sub-command. // CmdDocs represents the available docs sub-command.
@ -32,16 +30,16 @@ var CmdDocs = &cli.Command{
}, },
} }
func runDocs(_ context.Context, cmd *cli.Command) error { func runDocs(ctx *cli.Context) error {
docs, err := cli_docs.ToMarkdown(cmd.Root()) docs, err := ctx.App.ToMarkdown()
if cmd.Bool("man") { if ctx.Bool("man") {
docs, err = cli_docs.ToMan(cmd.Root()) docs, err = ctx.App.ToMan()
} }
if err != nil { if err != nil {
return err return err
} }
if !cmd.Bool("man") { if !ctx.Bool("man") {
// Clean up markdown. The following bug was fixed in v2, but is present in v1. // Clean up markdown. The following bug was fixed in v2, but is present in v1.
// It affects markdown output (even though the issue is referring to man pages) // It affects markdown output (even though the issue is referring to man pages)
// https://github.com/urfave/cli/issues/1040 // https://github.com/urfave/cli/issues/1040
@ -53,8 +51,8 @@ func runDocs(_ context.Context, cmd *cli.Command) error {
} }
out := os.Stdout out := os.Stdout
if cmd.String("output") != "" { if ctx.String("output") != "" {
fi, err := os.Create(cmd.String("output")) fi, err := os.Create(ctx.String("output"))
if err != nil { if err != nil {
return err return err
} }

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
golog "log" golog "log"
"os" "os"
@ -20,7 +19,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -30,7 +29,7 @@ var CmdDoctor = &cli.Command{
Usage: "Diagnose and optionally fix problems, convert or re-create database tables", Usage: "Diagnose and optionally fix problems, convert or re-create database tables",
Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
cmdDoctorCheck, cmdDoctorCheck,
cmdRecreateTable, cmdRecreateTable,
cmdDoctorConvert, cmdDoctorConvert,
@ -93,13 +92,16 @@ You should back-up your database before doing this and ensure that your database
Action: runRecreateTable, Action: runRecreateTable,
} }
func runRecreateTable(ctx context.Context, cmd *cli.Command) error { func runRecreateTable(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
// Redirect the default golog to here // Redirect the default golog to here
golog.SetFlags(0) golog.SetFlags(0)
golog.SetPrefix("") golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
debug := cmd.Bool("debug") debug := ctx.Bool("debug")
setting.MustInstalled() setting.MustInstalled()
setting.LoadDBSetting() setting.LoadDBSetting()
@ -110,15 +112,15 @@ func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
} }
setting.Database.LogSQL = debug setting.Database.LogSQL = debug
if err := db.InitEngine(ctx); err != nil { if err := db.InitEngine(stdCtx); err != nil {
fmt.Println(err) fmt.Println(err)
fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil return nil
} }
args := cmd.Args() args := ctx.Args()
names := make([]string, 0, cmd.NArg()) names := make([]string, 0, ctx.NArg())
for i := 0; i < cmd.NArg(); i++ { for i := 0; i < ctx.NArg(); i++ {
names = append(names, args.Get(i)) names = append(names, args.Get(i))
} }
@ -128,25 +130,24 @@ func runRecreateTable(ctx context.Context, cmd *cli.Command) error {
} }
recreateTables := migrate_base.RecreateTables(beans...) recreateTables := migrate_base.RecreateTables(beans...)
return db.InitEngineWithMigration(ctx, func(ctx context.Context, x *xorm.Engine) error { return db.InitEngineWithMigration(stdCtx, func(x *xorm.Engine) error {
if err := migrations.EnsureUpToDate(ctx, x); err != nil { if err := migrations.EnsureUpToDate(x); err != nil {
return err return err
} }
return recreateTables(x) return recreateTables(x)
}) })
} }
func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) { func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
// Silence the default loggers // Silence the default loggers
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
logFile := cmd.String("log-file") logFile := ctx.String("log-file")
switch logFile { if logFile == "" {
case "":
return // if no doctor log-file is set, do not show any log from default logger return // if no doctor log-file is set, do not show any log from default logger
case "-": } else if logFile == "-" {
setupConsoleLogger(log.TRACE, colorize, os.Stdout) setupConsoleLogger(log.TRACE, colorize, os.Stdout)
default: } else {
logFile, _ = filepath.Abs(logFile) logFile, _ = filepath.Abs(logFile)
writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}} writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}}
writer, err := log.NewEventWriter("console-to-file", "file", writeMode) writer, err := log.NewEventWriter("console-to-file", "file", writeMode)
@ -158,20 +159,23 @@ func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
} }
} }
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error { func runDoctorCheck(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
colorize := log.CanColorStdout colorize := log.CanColorStdout
if cmd.IsSet("color") { if ctx.IsSet("color") {
colorize = cmd.Bool("color") colorize = ctx.Bool("color")
} }
setupDoctorDefaultLogger(cmd, colorize) setupDoctorDefaultLogger(ctx, colorize)
// Finally redirect the default golang's log to here // Finally redirect the default golang's log to here
golog.SetFlags(0) golog.SetFlags(0)
golog.SetPrefix("") golog.SetPrefix("")
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
if cmd.IsSet("list") { if ctx.IsSet("list") {
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
_, _ = w.Write([]byte("Default\tName\tTitle\n")) _, _ = w.Write([]byte("Default\tName\tTitle\n"))
doctor.SortChecks(doctor.Checks) doctor.SortChecks(doctor.Checks)
@ -189,12 +193,12 @@ func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
} }
var checks []*doctor.Check var checks []*doctor.Check
if cmd.Bool("all") { if ctx.Bool("all") {
checks = make([]*doctor.Check, len(doctor.Checks)) checks = make([]*doctor.Check, len(doctor.Checks))
copy(checks, doctor.Checks) copy(checks, doctor.Checks)
} else if cmd.IsSet("run") { } else if ctx.IsSet("run") {
addDefault := cmd.Bool("default") addDefault := ctx.Bool("default")
runNamesSet := container.SetOf(cmd.StringSlice("run")...) runNamesSet := container.SetOf(ctx.StringSlice("run")...)
for _, check := range doctor.Checks { for _, check := range doctor.Checks {
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check) checks = append(checks, check)
@ -211,5 +215,5 @@ func runDoctorCheck(ctx context.Context, cmd *cli.Command) error {
} }
} }
} }
return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks) return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
} }

View file

@ -4,14 +4,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdDoctorConvert represents the available convert sub-command. // cmdDoctorConvert represents the available convert sub-command.
@ -22,8 +21,11 @@ var cmdDoctorConvert = &cli.Command{
Action: runDoctorConvert, Action: runDoctorConvert,
} }
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { func runDoctorConvert(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }

View file

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/services/doctor" "code.gitea.io/gitea/services/doctor"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestDoctorRun(t *testing.T) { func TestDoctorRun(t *testing.T) {
@ -22,13 +22,12 @@ func TestDoctorRun(t *testing.T) {
SkipDatabaseInitialization: true, SkipDatabaseInitialization: true,
}) })
app := &cli.Command{ app := cli.NewApp()
Commands: []*cli.Command{cmdDoctorCheck}, app.Commands = []*cli.Command{cmdDoctorCheck}
} err := app.Run([]string{"./gitea", "check", "--run", "test-check"})
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err) assert.NoError(t, err)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`) assert.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`) assert.ErrorContains(t, err, `unknown checks: "no-such"`)
} }

View file

@ -5,7 +5,7 @@
package cmd package cmd
import ( import (
"context" "fmt"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -21,7 +21,7 @@ import (
"gitea.com/go-chi/session" "gitea.com/go-chi/session"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
@ -93,7 +93,7 @@ var CmdDump = &cli.Command{
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "type", Name: "type",
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")),
}, },
}, },
} }
@ -102,17 +102,17 @@ func fatal(format string, args ...any) {
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(ctx context.Context, cmd *cli.Command) error { func runDump(ctx *cli.Context) error {
setting.MustInstalled() setting.MustInstalled()
quite := cmd.Bool("quiet") quite := ctx.Bool("quiet")
verbose := cmd.Bool("verbose") verbose := ctx.Bool("verbose")
if verbose && quite { if verbose && quite {
fatal("Option --quiet and --verbose cannot both be set") fatal("Option --quiet and --verbose cannot both be set")
} }
// outFileName is either "-" or a file name (will be made absolute) // outFileName is either "-" or a file name (will be made absolute)
outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type")) outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type"))
if outType == "" { if outType == "" {
fatal("Invalid output type") fatal("Invalid output type")
} }
@ -137,7 +137,10 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
setting.DisableLoggerInit() setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise setting.LoadSettings() // cannot access session settings otherwise
err := db.InitEngine(ctx) stdCtx, cancel := installSignals()
defer cancel()
err := db.InitEngine(stdCtx)
if err != nil { if err != nil {
return err return err
} }
@ -163,7 +166,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
} }
dumper.GlobalExcludeAbsPath(outFileName) dumper.GlobalExcludeAbsPath(outFileName)
if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") { if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
log.Info("Skip dumping local repositories") log.Info("Skip dumping local repositories")
} else { } else {
log.Info("Dumping local repositories... %s", setting.RepoRootPath) log.Info("Dumping local repositories... %s", setting.RepoRootPath)
@ -171,7 +174,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to include repositories: %v", err) fatal("Failed to include repositories: %v", err)
} }
if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") { if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") {
log.Info("Skip dumping LFS data") log.Info("Skip dumping LFS data")
} else if !setting.LFS.StartServer { } else if !setting.LFS.StartServer {
log.Info("LFS isn't enabled. Skip dumping LFS data") log.Info("LFS isn't enabled. Skip dumping LFS data")
@ -186,12 +189,12 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
} }
} }
if cmd.Bool("skip-db") { if ctx.Bool("skip-db") {
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere.
dumper.GlobalExcludeAbsPath(setting.Database.Path) dumper.GlobalExcludeAbsPath(setting.Database.Path)
log.Info("Skipping database") log.Info("Skipping database")
} else { } else {
tmpDir := cmd.String("tempdir") tmpDir := ctx.String("tempdir")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) { if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
fatal("Path does not exist: %s", tmpDir) fatal("Path does not exist: %s", tmpDir)
} }
@ -207,7 +210,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
} }
}() }()
targetDBType := cmd.String("database") targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else { } else {
@ -228,7 +231,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to include specified app.ini: %v", err) fatal("Failed to include specified app.ini: %v", err)
} }
if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") { if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
log.Info("Skipping custom directory") log.Info("Skipping custom directory")
} else { } else {
customDir, err := os.Stat(setting.CustomPath) customDir, err := os.Stat(setting.CustomPath)
@ -261,7 +264,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, opts.ProviderConfig) excludes = append(excludes, opts.ProviderConfig)
} }
if cmd.IsSet("skip-index") && cmd.Bool("skip-index") { if ctx.IsSet("skip-index") && ctx.Bool("skip-index") {
excludes = append(excludes, setting.Indexer.RepoPath) excludes = append(excludes, setting.Indexer.RepoPath)
excludes = append(excludes, setting.Indexer.IssuePath) excludes = append(excludes, setting.Indexer.IssuePath)
} }
@ -276,7 +279,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
} }
} }
if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") { if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") {
log.Info("Skip dumping attachment data") log.Info("Skip dumping attachment data")
} else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error {
info, err := object.Stat() info, err := object.Stat()
@ -288,7 +291,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
fatal("Failed to dump attachments: %v", err) fatal("Failed to dump attachments: %v", err)
} }
if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") { if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") {
log.Info("Skip dumping package data") log.Info("Skip dumping package data")
} else if !setting.Packages.Enabled { } else if !setting.Packages.Enabled {
log.Info("Packages isn't enabled. Skip dumping package data") log.Info("Packages isn't enabled. Skip dumping package data")
@ -305,7 +308,7 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
// Doesn't check if LogRootPath exists before processing --skip-log intentionally, // Doesn't check if LogRootPath exists before processing --skip-log intentionally,
// ensuring that it's clear the dump is skipped whether the directory's initialized // ensuring that it's clear the dump is skipped whether the directory's initialized
// yet or not. // yet or not.
if cmd.IsSet("skip-log") && cmd.Bool("skip-log") { if ctx.IsSet("skip-log") && ctx.Bool("skip-log") {
log.Info("Skip dumping log files") log.Info("Skip dumping log files")
} else { } else {
isExist, err := util.IsExist(setting.Log.RootPath) isExist, err := util.IsExist(setting.Log.RootPath)

View file

@ -19,7 +19,7 @@ import (
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdDumpRepository represents the available dump repository sub-command. // CmdDumpRepository represents the available dump repository sub-command.
@ -79,13 +79,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
}, },
} }
func runDumpRepository(ctx context.Context, cmd *cli.Command) error { func runDumpRepository(ctx *cli.Context) error {
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) stdCtx, cancel := installSignals()
defer cancel()
setting.DisableLoggerInit() if err := initDB(stdCtx); err != nil {
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
if err := initDB(ctx); err != nil {
return err return err
} }
@ -102,8 +100,8 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
var ( var (
serviceType structs.GitServiceType serviceType structs.GitServiceType
cloneAddr = cmd.String("clone_addr") cloneAddr = ctx.String("clone_addr")
serviceStr = cmd.String("git_service") serviceStr = ctx.String("git_service")
) )
if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") { if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") {
@ -121,13 +119,13 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
opts := base.MigrateOptions{ opts := base.MigrateOptions{
GitServiceType: serviceType, GitServiceType: serviceType,
CloneAddr: cloneAddr, CloneAddr: cloneAddr,
AuthUsername: cmd.String("auth_username"), AuthUsername: ctx.String("auth_username"),
AuthPassword: cmd.String("auth_password"), AuthPassword: ctx.String("auth_password"),
AuthToken: cmd.String("auth_token"), AuthToken: ctx.String("auth_token"),
RepoName: cmd.String("repo_name"), RepoName: ctx.String("repo_name"),
} }
if len(cmd.String("units")) == 0 { if len(ctx.String("units")) == 0 {
opts.Wiki = true opts.Wiki = true
opts.Issues = true opts.Issues = true
opts.Milestones = true opts.Milestones = true
@ -137,8 +135,8 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
opts.PullRequests = true opts.PullRequests = true
opts.ReleaseAssets = true opts.ReleaseAssets = true
} else { } else {
units := strings.SplitSeq(cmd.String("units"), ",") units := strings.Split(ctx.String("units"), ",")
for unit := range units { for _, unit := range units {
switch strings.ToLower(strings.TrimSpace(unit)) { switch strings.ToLower(strings.TrimSpace(unit)) {
case "": case "":
continue continue
@ -166,7 +164,7 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
// the repo_dir will be removed if error occurs in DumpRepository // the repo_dir will be removed if error occurs in DumpRepository
// make sure the directory doesn't exist or is empty, prevent from deleting user files // make sure the directory doesn't exist or is empty, prevent from deleting user files
repoDir := cmd.String("repo_dir") repoDir := ctx.String("repo_dir")
if exists, err := util.IsExist(repoDir); err != nil { if exists, err := util.IsExist(repoDir); err != nil {
return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err) return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err)
} else if exists { } else if exists {
@ -181,7 +179,7 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error {
if err := migrations.DumpRepository( if err := migrations.DumpRepository(
context.Background(), context.Background(),
repoDir, repoDir,
cmd.String("owner_name"), ctx.String("owner_name"),
opts, opts,
); err != nil { ); err != nil {
log.Fatal("Failed to dump repository: %v", err) log.Fatal("Failed to dump repository: %v", err)

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -20,7 +19,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdEmbedded represents the available extract sub-command. // CmdEmbedded represents the available extract sub-command.
@ -29,7 +28,7 @@ var (
Name: "embedded", Name: "embedded",
Usage: "Extract embedded resources", Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images", Description: "A command for extracting embedded resources, like templates and images",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdList, subcmdList,
subcmdView, subcmdView,
subcmdExtract, subcmdExtract,
@ -101,7 +100,7 @@ type assetFile struct {
path string path string
} }
func initEmbeddedExtractor(c *cli.Command) error { func initEmbeddedExtractor(c *cli.Context) error {
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
patterns, err := compileCollectPatterns(c.Args().Slice()) patterns, err := compileCollectPatterns(c.Args().Slice())
@ -116,31 +115,31 @@ func initEmbeddedExtractor(c *cli.Command) error {
return nil return nil
} }
func runList(_ context.Context, c *cli.Command) error { func runList(c *cli.Context) error {
if err := runListDo(c); err != nil { if err := runListDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runView(_ context.Context, c *cli.Command) error { func runView(c *cli.Context) error {
if err := runViewDo(c); err != nil { if err := runViewDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runExtract(_ context.Context, c *cli.Command) error { func runExtract(c *cli.Context) error {
if err := runExtractDo(c); err != nil { if err := runExtractDo(c); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err) fmt.Fprintf(os.Stderr, "%v\n", err)
return err return err
} }
return nil return nil
} }
func runListDo(c *cli.Command) error { func runListDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -152,7 +151,7 @@ func runListDo(c *cli.Command) error {
return nil return nil
} }
func runViewDo(c *cli.Command) error { func runViewDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -175,7 +174,7 @@ func runViewDo(c *cli.Command) error {
return nil return nil
} }
func runExtractDo(c *cli.Command) error { func runExtractDo(c *cli.Context) error {
if err := initEmbeddedExtractor(c); err != nil { if err := initEmbeddedExtractor(c); err != nil {
return err return err
} }
@ -217,7 +216,7 @@ func runExtractDo(c *cli.Command) error {
for _, a := range matchedAssetFiles { for _, a := range matchedAssetFiles {
if err := extractAsset(destdir, a, overwrite, rename); err != nil { if err := extractAsset(destdir, a, overwrite, rename); err != nil {
// Non-fatal error // Non-fatal error
_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", a.path, err) fmt.Fprintf(os.Stderr, "%s: %v", a.path, err)
} }
} }
@ -272,7 +271,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error {
return nil return nil
} }
func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
fs := assetfs.Layered(layer) fs := assetfs.Layered(layer)
files, err := fs.ListAllFiles(".", true) files, err := fs.ListAllFiles(".", true)
if err != nil { if err != nil {
@ -295,14 +294,16 @@ func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string,
} }
} }
func compileCollectPatterns(args []string) (_ []glob.Glob, err error) { func compileCollectPatterns(args []string) ([]glob.Glob, error) {
if len(args) == 0 { if len(args) == 0 {
args = []string{"**"} args = []string{"**"}
} }
pat := make([]glob.Glob, len(args)) pat := make([]glob.Glob, len(args))
for i := range args { for i := range args {
if pat[i], err = glob.Compile(args[i], '/'); err != nil { if g, err := glob.Compile(args[i], '/'); err != nil {
return nil, fmt.Errorf("invalid glob patterh %q: %w", args[i], err) return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
} else { //nolint:revive
pat[i] = g
} }
} }
return pat, nil return pat, nil

View file

@ -5,14 +5,13 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -20,7 +19,7 @@ var (
CmdGenerate = &cli.Command{ CmdGenerate = &cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens", Usage: "Generate Gitea's secrets/keys/tokens",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdSecret, subcmdSecret,
}, },
} }
@ -28,7 +27,7 @@ var (
subcmdSecret = &cli.Command{ subcmdSecret = &cli.Command{
Name: "secret", Name: "secret",
Usage: "Generate a secret token", Usage: "Generate a secret token",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
microcmdGenerateInternalToken, microcmdGenerateInternalToken,
microcmdGenerateLfsJwtSecret, microcmdGenerateLfsJwtSecret,
microcmdGenerateSecretKey, microcmdGenerateSecretKey,
@ -55,7 +54,7 @@ var (
} }
) )
func runGenerateInternalToken(_ context.Context, c *cli.Command) error { func runGenerateInternalToken(c *cli.Context) error {
internalToken, err := generate.NewInternalToken() internalToken, err := generate.NewInternalToken()
if err != nil { if err != nil {
return err return err
@ -70,7 +69,7 @@ func runGenerateInternalToken(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { func runGenerateLfsJwtSecret(c *cli.Context) error {
_, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64()
if err != nil { if err != nil {
return err return err
@ -85,7 +84,7 @@ func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runGenerateSecretKey(_ context.Context, c *cli.Command) error { func runGenerateSecretKey(c *cli.Context) error {
secretKey, err := generate.NewSecretKey() secretKey, err := generate.NewSecretKey()
if err != nil { if err != nil {
return err return err

View file

@ -20,11 +20,11 @@ import (
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
const ( const (
hookBatchSize = 500 hookBatchSize = 30
) )
var ( var (
@ -34,7 +34,7 @@ var (
Usage: "(internal) Should only be called by Git", Usage: "(internal) Should only be called by Git",
Description: "Delegate commands to corresponding Git hooks", Description: "Delegate commands to corresponding Git hooks",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdHookPreReceive, subcmdHookPreReceive,
subcmdHookUpdate, subcmdHookUpdate,
subcmdHookPostReceive, subcmdHookPostReceive,
@ -161,10 +161,12 @@ func (n *nilWriter) WriteString(s string) (int, error) {
return len(s), nil return len(s), nil
} }
func runHookPreReceive(ctx context.Context, c *cli.Command) error { func runHookPreReceive(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
@ -290,7 +292,7 @@ Gitea or set your environment appropriately.`, "")
// runHookUpdate avoid to do heavy operations on update hook because it will be // runHookUpdate avoid to do heavy operations on update hook because it will be
// invoked for every ref update which does not like pre-receive and post-receive // invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error { func runHookUpdate(c *cli.Context) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil return nil
} }
@ -307,12 +309,15 @@ func runHookUpdate(_ context.Context, c *cli.Command) error {
return nil return nil
} }
func runHookPostReceive(ctx context.Context, c *cli.Command) error { func runHookPostReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
// First of all run update-server-info no matter what // First of all run update-server-info no matter what
if _, _, err := git.NewCommand("update-server-info").RunStdString(ctx, nil); err != nil { if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
return fmt.Errorf("failed to call 'git update-server-info': %w", err) return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
} }
// Now if we're an internal don't do anything else // Now if we're an internal don't do anything else
@ -480,7 +485,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) {
func pushOptions() map[string]string { func pushOptions() map[string]string {
opts := make(map[string]string) opts := make(map[string]string)
if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil { if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
for idx := range pushCount { for idx := 0; idx < pushCount; idx++ {
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx)) opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
kv := strings.SplitN(opt, "=", 2) kv := strings.SplitN(opt, "=", 2)
if len(kv) == 2 { if len(kv) == 2 {
@ -491,7 +496,10 @@ func pushOptions() map[string]string {
return opts return opts
} }
func runHookProcReceive(ctx context.Context, c *cli.Command) error { func runHookProcReceive(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
@ -732,7 +740,7 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType)
// read prefix // read prefix
lengthBytes := make([]byte, 4) lengthBytes := make([]byte, 4)
for i := range 4 { for i := 0; i < 4; i++ {
lengthBytes[i], err = in.ReadByte() lengthBytes[i], err = in.ReadByte()
if err != nil { if err != nil {
return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err) return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err)

View file

@ -6,6 +6,7 @@ package cmd
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"strings" "strings"
"testing" "testing"
@ -14,7 +15,7 @@ import (
func TestPktLine(t *testing.T) { func TestPktLine(t *testing.T) {
// test read // test read
ctx := t.Context() ctx := context.Background()
s := strings.NewReader("0000") s := strings.NewReader("0000")
r := bufio.NewReader(s) r := bufio.NewReader(s)
result, err := readPktLine(ctx, r, pktLineTypeFlush) result, err := readPktLine(ctx, r, pktLineTypeFlush)

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -12,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdKeys represents the available keys sub-command // CmdKeys represents the available keys sub-command
@ -50,7 +49,7 @@ var CmdKeys = &cli.Command{
}, },
} }
func runKeys(ctx context.Context, c *cli.Command) error { func runKeys(c *cli.Context) error {
if !c.IsSet("username") { if !c.IsSet("username") {
return errors.New("No username provided") return errors.New("No username provided")
} }
@ -69,6 +68,9 @@ func runKeys(ctx context.Context, c *cli.Command) error {
return errors.New("No key type and content provided") return errors.New("No key type and content provided")
} }
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content)
@ -76,6 +78,6 @@ func runKeys(ctx context.Context, c *cli.Command) error {
if extra.Error != nil { if extra.Error != nil {
return extra.Error return extra.Error
} }
_, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text))
return nil return nil
} }

View file

@ -4,18 +4,24 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func runSendMail(ctx context.Context, c *cli.Command) error { func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
if err := argsSet(c, "title"); err != nil {
return err
}
subject := c.String("title") subject := c.String("title")
confirmSkiped := c.Bool("force") confirmSkiped := c.Bool("force")
body := c.String("content") body := c.String("content")

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -12,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// cmdHelp is our own help subcommand with more information // cmdHelp is our own help subcommand with more information
@ -23,18 +22,18 @@ func cmdHelp() *cli.Command {
Aliases: []string{"h"}, Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command", Usage: "Shows a list of commands or help for one command",
ArgsUsage: "[command]", ArgsUsage: "[command]",
Action: func(ctx context.Context, c *cli.Command) (err error) { Action: func(c *cli.Context) (err error) {
lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil}
targetCmdIdx := 0 targetCmdIdx := 0
if c.Name == "help" { if c.Command.Name == "help" {
targetCmdIdx = 1 targetCmdIdx = 1
} }
if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() { if lineage[targetCmdIdx+1].Command != nil {
err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */) err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name)
} else { } else {
err = cli.ShowAppHelp(c) err = cli.ShowAppHelp(c)
} }
_, _ = fmt.Fprintf(c.Root().Writer, ` _, _ = fmt.Fprintf(c.App.Writer, `
DEFAULT CONFIGURATION: DEFAULT CONFIGURATION:
AppPath: %s AppPath: %s
WorkPath: %s WorkPath: %s
@ -75,25 +74,25 @@ func appGlobalFlags() []cli.Flag {
} }
} }
func prepareSubcommandWithGlobalFlags(command *cli.Command) { func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) {
command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...) command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...)
command.Action = prepareWorkPathAndCustomConf(command.Action) command.Action = prepareWorkPathAndCustomConf(command.Action)
command.HideHelp = true command.HideHelp = true
if command.Name != "help" { if command.Name != "help" {
command.Commands = append(command.Commands, cmdHelp()) command.Subcommands = append(command.Subcommands, cmdHelp())
} }
for i := range command.Commands { for i := range command.Subcommands {
prepareSubcommandWithGlobalFlags(command.Commands[i]) prepareSubcommandWithConfig(command.Subcommands[i], globalFlags)
} }
} }
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error { func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error {
return func(ctx context.Context, cmd *cli.Command) error { return func(ctx *cli.Context) error {
var args setting.ArgWorkPathAndCustomConf var args setting.ArgWorkPathAndCustomConf
// from children to parent, check the global flags // from children to parent, check the global flags
for _, curCtx := range cmd.Lineage() { for _, curCtx := range ctx.Lineage() {
if curCtx.IsSet("work-path") && args.WorkPath == "" { if curCtx.IsSet("work-path") && args.WorkPath == "" {
args.WorkPath = curCtx.String("work-path") args.WorkPath = curCtx.String("work-path")
} }
@ -105,11 +104,11 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *
} }
} }
setting.InitWorkPathAndCommonConfig(os.Getenv, args) setting.InitWorkPathAndCommonConfig(os.Getenv, args)
if cmd.Bool("help") || action == nil { if ctx.Bool("help") || action == nil {
// the default behavior of "urfave/cli": "nil action" means "show help" // the default behavior of "urfave/cli": "nil action" means "show help"
return cmdHelp().Action(ctx, cmd) return cmdHelp().Action(ctx)
} }
return action(ctx, cmd) return action(ctx)
} }
} }
@ -118,13 +117,14 @@ type AppVersion struct {
Extra string Extra string
} }
func NewMainApp(appVer AppVersion) *cli.Command { func NewMainApp(appVer AppVersion) *cli.App {
app := &cli.Command{} app := cli.NewApp()
app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]" app.Name = "Gitea"
app.HelpName = "gitea"
app.Usage = "A painless self-hosted Git service" app.Usage = "A painless self-hosted Git service"
app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`
app.Version = appVer.Version + appVer.Extra app.Version = appVer.Version + appVer.Extra
app.EnableShellCompletion = true app.EnableBashCompletion = true
// these sub-commands need to use config file // these sub-commands need to use config file
subCmdWithConfig := []*cli.Command{ subCmdWithConfig := []*cli.Command{
@ -147,33 +147,29 @@ func NewMainApp(appVer AppVersion) *cli.Command {
// these sub-commands do not need the config file, and they do not depend on any path or environment variable. // these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{ subCmdStandalone := []*cli.Command{
cmdCert(), CmdCert,
CmdGenerate, CmdGenerate,
CmdDocs, CmdDocs,
} }
// TODO: we should eventually drop the default command,
// but not sure whether it would break Windows users who used to double-click the EXE to run.
app.DefaultCommand = CmdWeb.Name app.DefaultCommand = CmdWeb.Name
globalFlags := appGlobalFlags()
app.Flags = append(app.Flags, cli.VersionFlag) app.Flags = append(app.Flags, cli.VersionFlag)
app.Flags = append(app.Flags, appGlobalFlags()...) app.Flags = append(app.Flags, globalFlags...)
app.HideHelp = true // use our own help action to show helps (with more information like default config) app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Before = PrepareConsoleLoggerLevel(log.INFO) app.Before = PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithConfig { for i := range subCmdWithConfig {
prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags)
} }
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
setting.InitGiteaEnvVars()
return app return app
} }
func RunMainApp(app *cli.Command, args ...string) error { func RunMainApp(app *cli.App, args ...string) error {
ctx, cancel := installSignals() err := app.Run(args)
defer cancel()
err := app.Run(ctx, args)
if err == nil { if err == nil {
return nil return nil
} }

View file

@ -4,10 +4,9 @@
package cmd package cmd
import ( import (
"context"
"errors"
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -17,7 +16,7 @@ import (
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -28,10 +27,10 @@ func makePathOutput(workPath, customPath, customConf string) string {
return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf)
} }
func newTestApp(testCmdAction cli.ActionFunc) *cli.Command { func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App {
app := NewMainApp(AppVersion{}) app := NewMainApp(AppVersion{})
testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction}
prepareSubcommandWithGlobalFlags(testCmd) prepareSubcommandWithConfig(testCmd, appGlobalFlags())
app.Commands = append(app.Commands, testCmd) app.Commands = append(app.Commands, testCmd)
app.DefaultCommand = testCmd.Name app.DefaultCommand = testCmd.Name
return app return app
@ -43,7 +42,7 @@ type runResult struct {
ExitCode int ExitCode int
} }
func runTestApp(app *cli.Command, args ...string) (runResult, error) { func runTestApp(app *cli.App, args ...string) (runResult, error) {
outBuf := new(strings.Builder) outBuf := new(strings.Builder)
errBuf := new(strings.Builder) errBuf := new(strings.Builder)
app.Writer = outBuf app.Writer = outBuf
@ -66,7 +65,7 @@ func TestCliCmd(t *testing.T) {
defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini")
cli.CommandHelpTemplate = "(command help template)" cli.CommandHelpTemplate = "(command help template)"
cli.RootCommandHelpTemplate = "(app help template)" cli.AppHelpTemplate = "(app help template)"
cli.SubcommandHelpTemplate = "(subcommand help template)" cli.SubcommandHelpTemplate = "(subcommand help template)"
cases := []struct { cases := []struct {
@ -110,50 +109,70 @@ func TestCliCmd(t *testing.T) {
}, },
} }
for _, c := range cases { app := newTestApp(func(ctx *cli.Context) error {
t.Run(c.cmd, func(t *testing.T) { _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil
_, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) })
return nil var envBackup []string
}) for _, s := range os.Environ() {
for k, v := range c.env { if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
t.Setenv(k, v) envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
} }
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough }
r, err := runTestApp(app, args...) }
assert.NoError(t, err, c.cmd) defer func() {
assert.NotEmpty(t, c.exp, c.cmd) clearGiteaEnv()
assert.Contains(t, r.Stdout, c.exp, c.cmd) for _, s := range envBackup {
}) k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases {
clearGiteaEnv()
for k, v := range c.env {
_ = os.Setenv(k, v)
}
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...)
assert.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd)
} }
} }
func TestCliCmdError(t *testing.T) { func TestCliCmdError(t *testing.T) {
app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }) app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") })
r, err := runTestApp(app, "./gitea", "test-cmd") r, err := runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Equal(t, "Command error: normal error\n", r.Stderr) assert.Equal(t, "Command error: normal error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }) app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 2, r.ExitCode) assert.Equal(t, 2, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Equal(t, "exit error\n", r.Stderr) assert.Equal(t, "exit error\n", r.Stderr)
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such")
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, 1, r.ExitCode) assert.Equal(t, 1, r.ExitCode)
assert.Empty(t, r.Stdout) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout)
assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr ....
app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) app = newTestApp(func(ctx *cli.Context) error { return nil })
r, err = runTestApp(app, "./gitea", "test-cmd") r, err = runTestApp(app, "./gitea", "test-cmd")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called
assert.Empty(t, r.Stdout) assert.Equal(t, "", r.Stdout)
assert.Empty(t, r.Stderr) assert.Equal(t, "", r.Stderr)
} }

View file

@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"os" "os"
"time" "time"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -19,7 +18,7 @@ var (
Name: "manager", Name: "manager",
Usage: "Manage the running gitea process", Usage: "Manage the running gitea process",
Description: "This is a command for managing the running gitea process", Description: "This is a command for managing the running gitea process",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
subcmdShutdown, subcmdShutdown,
subcmdRestart, subcmdRestart,
subcmdReloadTemplates, subcmdReloadTemplates,
@ -109,31 +108,46 @@ var (
} }
) )
func runShutdown(ctx context.Context, c *cli.Command) error { func runShutdown(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Shutdown(ctx) extra := private.Shutdown(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runRestart(ctx context.Context, c *cli.Command) error { func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Restart(ctx) extra := private.Restart(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runReloadTemplates(ctx context.Context, c *cli.Command) error { func runReloadTemplates(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.ReloadTemplates(ctx) extra := private.ReloadTemplates(ctx)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runFlushQueues(ctx context.Context, c *cli.Command) error { func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runProcesses(ctx context.Context, c *cli.Command) error { func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)

View file

@ -4,7 +4,6 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -12,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
var ( var (
@ -61,7 +60,7 @@ var (
subcmdLogging = &cli.Command{ subcmdLogging = &cli.Command{
Name: "logging", Name: "logging",
Usage: "Adjust logging commands", Usage: "Adjust logging commands",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "pause", Name: "pause",
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)",
@ -105,7 +104,7 @@ var (
}, { }, {
Name: "add", Name: "add",
Usage: "Add a logger", Usage: "Add a logger",
Commands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "file", Name: "file",
Usage: "Add a file logger", Usage: "Add a file logger",
@ -119,6 +118,7 @@ var (
Name: "rotate", Name: "rotate",
Aliases: []string{"r"}, Aliases: []string{"r"},
Usage: "Rotate logs", Usage: "Rotate logs",
Value: true,
}, },
&cli.Int64Flag{ &cli.Int64Flag{
Name: "max-size", Name: "max-size",
@ -129,6 +129,7 @@ var (
Name: "daily", Name: "daily",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Rotate logs daily", Usage: "Rotate logs daily",
Value: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "max-days", Name: "max-days",
@ -139,6 +140,7 @@ var (
Name: "compress", Name: "compress",
Aliases: []string{"z"}, Aliases: []string{"z"},
Usage: "Compress rotated logs", Usage: "Compress rotated logs",
Value: true,
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "compression-level", Name: "compression-level",
@ -193,7 +195,10 @@ var (
} }
) )
func runRemoveLogger(ctx context.Context, c *cli.Command) error { func runRemoveLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
logger := c.String("logger") logger := c.String("logger")
if len(logger) == 0 { if len(logger) == 0 {
@ -205,7 +210,10 @@ func runRemoveLogger(ctx context.Context, c *cli.Command) error {
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runAddConnLogger(ctx context.Context, c *cli.Command) error { func runAddConnLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "conn" mode := "conn"
@ -229,10 +237,13 @@ func runAddConnLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("reconnect-on-message") { if c.IsSet("reconnect-on-message") {
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") vals["reconnectOnMsg"] = c.Bool("reconnect-on-message")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func runAddFileLogger(ctx context.Context, c *cli.Command) error { func runAddFileLogger(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
vals := map[string]any{} vals := map[string]any{}
mode := "file" mode := "file"
@ -259,10 +270,10 @@ func runAddFileLogger(ctx context.Context, c *cli.Command) error {
if c.IsSet("compression-level") { if c.IsSet("compression-level") {
vals["compressionLevel"] = c.Int("compression-level") vals["compressionLevel"] = c.Int("compression-level")
} }
return commonAddLogger(ctx, c, mode, vals) return commonAddLogger(c, mode, vals)
} }
func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error {
if len(c.String("level")) > 0 { if len(c.String("level")) > 0 {
vals["level"] = log.LevelFromString(c.String("level")).String() vals["level"] = log.LevelFromString(c.String("level")).String()
} }
@ -289,33 +300,46 @@ func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[
if c.IsSet("writer") { if c.IsSet("writer") {
writer = c.String("writer") writer = c.String("writer")
} }
ctx, cancel := installSignals()
defer cancel()
extra := private.AddLogger(ctx, logger, writer, mode, vals) extra := private.AddLogger(ctx, logger, writer, mode, vals)
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
func runPauseLogging(ctx context.Context, c *cli.Command) error { func runPauseLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.PauseLogging(ctx) userMsg := private.PauseLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runResumeLogging(ctx context.Context, c *cli.Command) error { func runResumeLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.ResumeLogging(ctx) userMsg := private.ResumeLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { func runReleaseReopenLogging(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
userMsg := private.ReleaseReopenLogging(ctx) userMsg := private.ReleaseReopenLogging(ctx)
_, _ = fmt.Fprintln(os.Stdout, userMsg) _, _ = fmt.Fprintln(os.Stdout, userMsg)
return nil return nil
} }
func runSetLogSQL(ctx context.Context, c *cli.Command) error { func runSetLogSQL(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
extra := private.SetLogSQL(ctx, !c.Bool("off")) extra := private.SetLogSQL(ctx, !c.Bool("off"))

View file

@ -7,11 +7,11 @@ import (
"context" "context"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/versioned_migration"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdMigrate represents the available migrate sub-command. // CmdMigrate represents the available migrate sub-command.
@ -22,8 +22,11 @@ var CmdMigrate = &cli.Command{
Action: runMigrate, Action: runMigrate,
} }
func runMigrate(ctx context.Context, c *cli.Command) error { func runMigrate(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
@ -33,7 +36,7 @@ func runMigrate(ctx context.Context, c *cli.Command) error {
log.Info("Log path: %s", setting.Log.RootPath) log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf) log.Info("Configuration file: %s", setting.CustomConf)
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil { if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }

View file

@ -13,6 +13,7 @@ import (
actions_model "code.gitea.io/gitea/models/actions" actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/migrations"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -20,9 +21,8 @@ import (
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/versioned_migration"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdMigrateStorage represents the available migrate storage sub-command. // CmdMigrateStorage represents the available migrate storage sub-command.
@ -196,7 +196,7 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error { return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
if artifact.Status == actions_model.ArtifactStatusExpired { if artifact.Status == int64(actions_model.ArtifactStatusExpired) {
return nil return nil
} }
@ -213,8 +213,11 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora
}) })
} }
func runMigrateStorage(ctx context.Context, cmd *cli.Command) error { func runMigrateStorage(ctx *cli.Context) error {
if err := initDB(ctx); err != nil { stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err return err
} }
@ -224,7 +227,7 @@ func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
log.Info("Log path: %s", setting.Log.RootPath) log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf) log.Info("Configuration file: %s", setting.CustomConf)
if err := db.InitEngineWithMigration(context.Background(), versioned_migration.Migrate); err != nil { if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err) log.Fatal("Failed to initialize ORM engine: %v", err)
return err return err
} }
@ -235,51 +238,51 @@ func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
var dstStorage storage.ObjectStorage var dstStorage storage.ObjectStorage
var err error var err error
switch strings.ToLower(cmd.String("storage")) { switch strings.ToLower(ctx.String("storage")) {
case "": case "":
fallthrough fallthrough
case string(setting.LocalStorageType): case string(setting.LocalStorageType):
p := cmd.String("path") p := ctx.String("path")
if p == "" { if p == "" {
log.Fatal("Path must be given when storage is local") log.Fatal("Path must be given when storage is local")
return nil return nil
} }
dstStorage, err = storage.NewLocalStorage( dstStorage, err = storage.NewLocalStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
Path: p, Path: p,
}) })
case string(setting.MinioStorageType): case string(setting.MinioStorageType):
dstStorage, err = storage.NewMinioStorage( dstStorage, err = storage.NewMinioStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
MinioConfig: setting.MinioStorageConfig{ MinioConfig: setting.MinioStorageConfig{
Endpoint: cmd.String("minio-endpoint"), Endpoint: ctx.String("minio-endpoint"),
AccessKeyID: cmd.String("minio-access-key-id"), AccessKeyID: ctx.String("minio-access-key-id"),
SecretAccessKey: cmd.String("minio-secret-access-key"), SecretAccessKey: ctx.String("minio-secret-access-key"),
Bucket: cmd.String("minio-bucket"), Bucket: ctx.String("minio-bucket"),
Location: cmd.String("minio-location"), Location: ctx.String("minio-location"),
BasePath: cmd.String("minio-base-path"), BasePath: ctx.String("minio-base-path"),
UseSSL: cmd.Bool("minio-use-ssl"), UseSSL: ctx.Bool("minio-use-ssl"),
InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"), InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"),
ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"), ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"),
BucketLookUpType: cmd.String("minio-bucket-lookup-type"), BucketLookUpType: ctx.String("minio-bucket-lookup-type"),
}, },
}) })
case string(setting.AzureBlobStorageType): case string(setting.AzureBlobStorageType):
dstStorage, err = storage.NewAzureBlobStorage( dstStorage, err = storage.NewAzureBlobStorage(
ctx, stdCtx,
&setting.Storage{ &setting.Storage{
AzureBlobConfig: setting.AzureBlobStorageConfig{ AzureBlobConfig: setting.AzureBlobStorageConfig{
Endpoint: cmd.String("azureblob-endpoint"), Endpoint: ctx.String("azureblob-endpoint"),
AccountName: cmd.String("azureblob-account-name"), AccountName: ctx.String("azureblob-account-name"),
AccountKey: cmd.String("azureblob-account-key"), AccountKey: ctx.String("azureblob-account-key"),
Container: cmd.String("azureblob-container"), Container: ctx.String("azureblob-container"),
BasePath: cmd.String("azureblob-base-path"), BasePath: ctx.String("azureblob-base-path"),
}, },
}) })
default: default:
return fmt.Errorf("unsupported storage type: %s", cmd.String("storage")) return fmt.Errorf("unsupported storage type: %s", ctx.String("storage"))
} }
if err != nil { if err != nil {
return err return err
@ -296,14 +299,14 @@ func runMigrateStorage(ctx context.Context, cmd *cli.Command) error {
"actions-artifacts": migrateActionsArtifacts, "actions-artifacts": migrateActionsArtifacts,
} }
tp := strings.ToLower(cmd.String("type")) tp := strings.ToLower(ctx.String("type"))
if m, ok := migratedMethods[tp]; ok { if m, ok := migratedMethods[tp]; ok {
if err := m(ctx, dstStorage); err != nil { if err := m(stdCtx, dstStorage); err != nil {
return err return err
} }
log.Info("%s files have successfully been copied to the new storage.", tp) log.Info("%s files have successfully been copied to the new storage.", tp)
return nil return nil
} }
return fmt.Errorf("unsupported storage: %s", cmd.String("type")) return fmt.Errorf("unsupported storage: %s", ctx.String("type"))
} }

View file

@ -4,6 +4,7 @@
package cmd package cmd
import ( import (
"context"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -52,7 +53,7 @@ func TestMigratePackages(t *testing.T) {
assert.NotNil(t, v) assert.NotNil(t, v)
assert.NotNil(t, f) assert.NotNil(t, f)
ctx := t.Context() ctx := context.Background()
p := t.TempDir() p := t.TempDir()
@ -69,6 +70,6 @@ func TestMigratePackages(t *testing.T) {
entries, err := os.ReadDir(p) entries, err := os.ReadDir(p)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, 2) assert.Len(t, entries, 2)
assert.Equal(t, "01", entries[0].Name()) assert.EqualValues(t, "01", entries[0].Name())
assert.Equal(t, "tmp", entries[1].Name()) assert.EqualValues(t, "tmp", entries[1].Name())
} }

View file

@ -4,13 +4,12 @@
package cmd package cmd
import ( import (
"context"
"strings" "strings"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// CmdRestoreRepository represents the available restore a repository sub-command. // CmdRestoreRepository represents the available restore a repository sub-command.
@ -49,7 +48,10 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
}, },
} }
func runRestoreRepository(ctx context.Context, c *cli.Command) error { func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
setting.MustInstalled() setting.MustInstalled()
var units []string var units []string
if s := c.String("units"); s != "" { if s := c.String("units"); s != "" {

View file

@ -11,6 +11,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -19,7 +20,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer" "code.gitea.io/gitea/modules/lfstransfer"
@ -33,7 +34,15 @@ import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/kballard/go-shellquote" "github.com/kballard/go-shellquote"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
)
const (
verbUploadPack = "git-upload-pack"
verbUploadArchive = "git-upload-archive"
verbReceivePack = "git-receive-pack"
verbLfsAuthenticate = "git-lfs-authenticate"
verbLfsTransfer = "git-lfs-transfer"
) )
// CmdServ represents the available serv sub-command. // CmdServ represents the available serv sub-command.
@ -69,6 +78,22 @@ func setup(ctx context.Context, debug bool) {
} }
} }
var (
// keep getAccessMode() in sync
allowedCommands = container.SetOf(
verbUploadPack,
verbUploadArchive,
verbReceivePack,
verbLfsAuthenticate,
verbLfsTransfer,
)
allowedCommandsLfs = container.SetOf(
verbLfsAuthenticate,
verbLfsTransfer,
)
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
// fail prints message to stdout, it's mainly used for git serv and git hook commands. // fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user. // The output will be passed to git client and shown to user.
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error { func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
@ -79,10 +104,7 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error
// There appears to be a chance to cause a zombie process and failure to read the Exit status // There appears to be a chance to cause a zombie process and failure to read the Exit status
// if nothing is outputted on stdout. // if nothing is outputted on stdout.
_, _ = fmt.Fprintln(os.Stdout, "") _, _ = fmt.Fprintln(os.Stdout, "")
// add extra empty lines to separate our message from other git errors to get more attention _, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
_, _ = fmt.Fprintln(os.Stderr, "error:")
_, _ = fmt.Fprintln(os.Stderr, "error:", userMessage)
_, _ = fmt.Fprintln(os.Stderr, "error:")
if logMsgFmt != "" { if logMsgFmt != "" {
logMsg := fmt.Sprintf(logMsgFmt, args...) logMsg := fmt.Sprintf(logMsgFmt, args...)
@ -114,20 +136,19 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
func getAccessMode(verb, lfsVerb string) perm.AccessMode { func getAccessMode(verb, lfsVerb string) perm.AccessMode {
switch verb { switch verb {
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive: case verbUploadPack, verbUploadArchive:
return perm.AccessModeRead return perm.AccessModeRead
case git.CmdVerbReceivePack: case verbReceivePack:
return perm.AccessModeWrite return perm.AccessModeWrite
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer: case verbLfsAuthenticate, verbLfsTransfer:
switch lfsVerb { switch lfsVerb {
case git.CmdSubVerbLfsUpload: case "upload":
return perm.AccessModeWrite return perm.AccessModeWrite
case git.CmdSubVerbLfsDownload: case "download":
return perm.AccessModeRead return perm.AccessModeRead
} }
} }
// should be unreachable // should be unreachable
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
return perm.AccessModeNone return perm.AccessModeNone
} }
@ -149,10 +170,13 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC
if err != nil { if err != nil {
return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err) return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
} }
return "Bearer " + tokenString, nil return fmt.Sprintf("Bearer %s", tokenString), nil
} }
func runServ(ctx context.Context, c *cli.Command) error { func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
// FIXME: This needs to internationalised // FIXME: This needs to internationalised
setup(ctx, c.Bool("debug")) setup(ctx, c.Bool("debug"))
@ -203,37 +227,41 @@ func runServ(ctx context.Context, c *cli.Command) error {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND")) log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
} }
sshCmdArgs, err := shellquote.Split(cmd) words, err := shellquote.Split(cmd)
if err != nil { if err != nil {
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err) return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
} }
if len(sshCmdArgs) < 2 { if len(words) < 2 {
if git.DefaultFeatures().SupportProcReceive { if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow // for AGit Flow
if cmd == "ssh_info" { if cmd == "ssh_info" {
fmt.Print(`{"type":"agit","version":1}`) fmt.Print(`{"type":"gitea","version":1}`)
return nil return nil
} }
} }
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
} }
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/") verb := words[0]
repoPathFields := strings.SplitN(repoPath, "/", 2) repoPath := strings.TrimPrefix(words[1], "/")
if len(repoPathFields) != 2 {
var lfsVerb string
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
} }
username := repoPathFields[0] username := rr[0]
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki" reponame := strings.TrimSuffix(rr[1], ".git")
// LowerCase and trim the repoPath as that's how they are stored. // LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame // This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected. // so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath)) repoPath = strings.ToLower(strings.TrimSpace(repoPath))
if !repo.IsValidSSHAccessRepoName(reponame) { if alphaDashDotPattern.MatchString(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
} }
@ -255,23 +283,22 @@ func runServ(ctx context.Context, c *cli.Command) error {
}() }()
} }
verb, lfsVerb := sshCmdArgs[0], "" if allowedCommands.Contains(verb) {
if !git.IsAllowedVerbForServe(verb) { if allowedCommandsLfs.Contains(verb) {
if !setting.LFS.StartServer {
return fail(ctx, "LFS Server is not enabled", "")
}
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
return fail(ctx, "LFS SSH transfer is not enabled", "")
}
if len(words) > 2 {
lfsVerb = words[2]
}
}
} else {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb) return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
} }
if git.IsAllowedVerbForServeLfs(verb) {
if !setting.LFS.StartServer {
return fail(ctx, "LFS Server is not enabled", "")
}
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
return fail(ctx, "LFS SSH transfer is not enabled", "")
}
if len(sshCmdArgs) > 2 {
lfsVerb = sshCmdArgs[2]
}
}
requestedMode := getAccessMode(verb, lfsVerb) requestedMode := getAccessMode(verb, lfsVerb)
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb) results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
@ -280,7 +307,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
// LFS SSH protocol // LFS SSH protocol
if verb == git.CmdVerbLfsTransfer { if verb == verbLfsTransfer {
token, err := getLFSAuthToken(ctx, lfsVerb, results) token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil { if err != nil {
return err return err
@ -289,7 +316,7 @@ func runServ(ctx context.Context, c *cli.Command) error {
} }
// LFS token authentication // LFS token authentication
if verb == git.CmdVerbLfsAuthenticate { if verb == verbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName)) url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := getLFSAuthToken(ctx, lfsVerb, results) token, err := getLFSAuthToken(ctx, lfsVerb, results)
@ -342,9 +369,9 @@ func runServ(ctx context.Context, c *cli.Command) error {
repo_module.EnvPusherEmail+"="+results.UserEmail, repo_module.EnvPusherEmail+"="+results.UserEmail,
repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10), repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10), repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
repo_module.EnvPRID+"="+strconv.Itoa(0), repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0),
repo_module.EnvDeployKeyID+"="+strconv.FormatInt(results.DeployKeyID, 10), repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID),
repo_module.EnvKeyID+"="+strconv.FormatInt(results.KeyID, 10), repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID),
repo_module.EnvAppURL+"="+setting.AppURL, repo_module.EnvAppURL+"="+setting.AppURL,
) )
// to avoid breaking, here only use the minimal environment variables for the "gitea serv" command. // to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.

View file

@ -18,17 +18,15 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install" "code.gitea.io/gitea/routers/install"
"github.com/felixge/fgprof" "github.com/felixge/fgprof"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// PIDFile could be set from build tag // PIDFile could be set from build tag
@ -130,19 +128,19 @@ func showWebStartupMessage(msg string) {
} }
} }
func serveInstall(cmd *cli.Command) error { func serveInstall(ctx *cli.Context) error {
showWebStartupMessage("Prepare to run install page") showWebStartupMessage("Prepare to run install page")
routers.InitWebInstallPage(graceful.GetManager().HammerContext()) routers.InitWebInstallPage(graceful.GetManager().HammerContext())
// Flag for port number in case first time run conflict // Flag for port number in case first time run conflict
if cmd.IsSet("port") { if ctx.IsSet("port") {
if err := setPort(cmd.String("port")); err != nil { if err := setPort(ctx.String("port")); err != nil {
return err return err
} }
} }
if cmd.IsSet("install-port") { if ctx.IsSet("install-port") {
if err := setPort(cmd.String("install-port")); err != nil { if err := setPort(ctx.String("install-port")); err != nil {
return err return err
} }
} }
@ -163,7 +161,7 @@ func serveInstall(cmd *cli.Command) error {
return nil return nil
} }
func serveInstalled(c *cli.Command) error { func serveInstalled(ctx *cli.Context) error {
setting.InitCfgProvider(setting.CustomConf) setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings() setting.LoadCommonSettings()
setting.MustInstalled() setting.MustInstalled()
@ -213,19 +211,13 @@ func serveInstalled(c *cli.Command) error {
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath) log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
} }
// the AppDataTempDir is fully managed by us with a safe sub-path
// so it's safe to automatically remove the outdated files
setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
// Override the provided port number within the configuration // Override the provided port number within the configuration
if c.IsSet("port") { if ctx.IsSet("port") {
if err := setPort(c.String("port")); err != nil { if err := setPort(ctx.String("port")); err != nil {
return err return err
} }
} }
gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
// Set up Chi routes // Set up Chi routes
webRoutes := routers.NormalRoutes() webRoutes := routers.NormalRoutes()
err := listen(webRoutes, true) err := listen(webRoutes, true)
@ -244,17 +236,13 @@ func servePprof() {
finished() finished()
} }
func runWeb(_ context.Context, cmd *cli.Command) error { func runWeb(ctx *cli.Context) error {
defer func() { defer func() {
if panicked := recover(); panicked != nil { if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
} }
}() }()
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
return fmt.Errorf("unknown command: %s", subCmdName)
}
managerCtx, cancel := context.WithCancel(context.Background()) managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx) graceful.InitManager(managerCtx)
defer cancel() defer cancel()
@ -266,12 +254,12 @@ func runWeb(_ context.Context, cmd *cli.Command) error {
} }
// Set pid file setting // Set pid file setting
if cmd.IsSet("pid") { if ctx.IsSet("pid") {
createPIDFile(cmd.String("pid")) createPIDFile(ctx.String("pid"))
} }
if !setting.InstallLock { if !setting.InstallLock {
if err := serveInstall(cmd); err != nil { if err := serveInstall(ctx); err != nil {
return err return err
} }
} else { } else {
@ -282,7 +270,7 @@ func runWeb(_ context.Context, cmd *cli.Command) error {
go servePprof() go servePprof()
} }
return serveInstalled(cmd) return serveInstalled(ctx)
} }
func setPort(port string) error { func setPort(port string) error {

View file

@ -136,7 +136,7 @@ func runACME(listenAddr string, m http.Handler) error {
} }
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet && r.Method != http.MethodHead { if r.Method != "GET" && r.Method != "HEAD" {
http.Error(w, "Use HTTPS", http.StatusBadRequest) http.Error(w, "Use HTTPS", http.StatusBadRequest)
return return
} }

View file

@ -23,6 +23,12 @@ func NoHTTPRedirector() {
graceful.GetManager().InformCleanup() graceful.GetManager().InformCleanup()
} }
// NoMainListener tells our cleanup routine that we will not be using a possibly provided listener
// for our main HTTP/HTTPS service
func NoMainListener() {
graceful.GetManager().InformCleanup()
}
// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener // NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener
// for our install HTTP/HTTPS service // for our install HTTP/HTTPS service
func NoInstallListener() { func NoInstallListener() {

View file

@ -1,30 +1,31 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//nolint:forbidigo // use of print functions is allowed in cli //nolint:forbidigo
package main package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path" "path"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/google/go-github/v71/github" "github.com/google/go-github/v71/github"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const defaultVersion = "v1.18" // to backport to const defaultVersion = "v1.18" // to backport to
func main() { func main() {
app := &cli.Command{} app := cli.NewApp()
app.Name = "backport" app.Name = "backport"
app.Usage = "Backport provided PR-number on to the current or previous released version" app.Usage = "Backport provided PR-number on to the current or previous released version"
app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version` app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version`
@ -89,7 +90,7 @@ func main() {
Usage: "Set this flag to continue from a git cherry-pick that has broken", Usage: "Set this flag to continue from a git cherry-pick that has broken",
}, },
} }
cli.RootCommandHelpTemplate = `NAME: cli.AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}} {{.Name}} - {{.Usage}}
USAGE: USAGE:
{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
@ -103,12 +104,16 @@ OPTIONS:
` `
app.Action = runBackport app.Action = runBackport
if err := app.Run(context.Background(), os.Args); err != nil {
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err)
} }
} }
func runBackport(ctx context.Context, c *cli.Command) error { func runBackport(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
continuing := c.Bool("continue") continuing := c.Bool("continue")
var pr string var pr string
@ -153,7 +158,7 @@ func runBackport(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice() args := c.Args().Slice()
if len(args) == 0 && pr == "" { if len(args) == 0 && pr == "" {
return errors.New("no PR number provided\nProvide a PR number to backport") return fmt.Errorf("no PR number provided\nProvide a PR number to backport")
} else if len(args) != 1 && pr == "" { } else if len(args) != 1 && pr == "" {
return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args) return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args)
} }
@ -337,8 +342,8 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out)) fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out))
return "", "", fmt.Errorf("unable to determine forked remote: %w", err) return "", "", fmt.Errorf("unable to determine forked remote: %w", err)
} }
lines := strings.SplitSeq(string(out), "\n") lines := strings.Split(string(out), "\n")
for line := range lines { for _, line := range lines {
fields := strings.Split(line, "\t") fields := strings.Split(line, "\t")
name, remote := fields[0], fields[1] name, remote := fields[0], fields[1]
// only look at pushers // only look at pushers
@ -356,12 +361,12 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro
if !strings.Contains(remote, forkUser) { if !strings.Contains(remote, forkUser) {
continue continue
} }
if after, ok := strings.CutPrefix(remote, "git@github.com:"); ok { if strings.HasPrefix(remote, "git@github.com:") {
forkUser = after forkUser = strings.TrimPrefix(remote, "git@github.com:")
} else if after, ok := strings.CutPrefix(remote, "https://github.com/"); ok { } else if strings.HasPrefix(remote, "https://github.com/") {
forkUser = after forkUser = strings.TrimPrefix(remote, "https://github.com/")
} else if after, ok := strings.CutPrefix(remote, "https://www.github.com/"); ok { } else if strings.HasPrefix(remote, "https://www.github.com/") {
forkUser = after forkUser = strings.TrimPrefix(remote, "https://www.github.com/")
} else if forkUser == "" { } else if forkUser == "" {
return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote) return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote)
} }
@ -454,3 +459,25 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string,
return "", nil return "", nil
} }
func installSignals() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// install notify
signalChannel := make(chan os.Signal, 1)
signal.Notify(
signalChannel,
syscall.SIGINT,
syscall.SIGTERM,
)
select {
case <-signalChannel:
case <-ctx.Done():
}
cancel()
signal.Reset()
}()
return ctx, cancel
}

View file

@ -4,17 +4,16 @@
package main package main
import ( import (
"context"
"os" "os"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
func main() { func main() {
app := cli.Command{} app := cli.NewApp()
app.Name = "environment-to-ini" app.Name = "environment-to-ini"
app.Usage = "Use provided environment to update configuration ini" app.Usage = "Use provided environment to update configuration ini"
app.Description = `As a helper to allow docker users to update the gitea configuration app.Description = `As a helper to allow docker users to update the gitea configuration
@ -73,13 +72,13 @@ func main() {
}, },
} }
app.Action = runEnvironmentToIni app.Action = runEnvironmentToIni
err := app.Run(context.Background(), os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Fatal("Failed to run app with %s: %v", os.Args, err) log.Fatal("Failed to run app with %s: %v", os.Args, err)
} }
} }
func runEnvironmentToIni(_ context.Context, c *cli.Command) error { func runEnvironmentToIni(c *cli.Context) error {
// the config system may change the environment variables, so get a copy first, to be used later // the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...) env := append([]string{}, os.Environ()...)
setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{

View file

@ -59,22 +59,27 @@ RUN_USER = ; git
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; The protocol the server listens on. One of "http", "https", "http+unix", "fcgi" or "fcgi+unix". ;; The protocol the server listens on. One of 'http', 'https', 'http+unix', 'fcgi' or 'fcgi+unix'. Defaults to 'http'
;; Note: Value must be lowercase.
;PROTOCOL = http ;PROTOCOL = http
;; ;;
;; Set the domain for the server. ;; Expect PROXY protocol headers on connections
;USE_PROXY_PROTOCOL = false
;;
;; Use PROXY protocol in TLS Bridging mode
;PROXY_PROTOCOL_TLS_BRIDGING = false
;;
; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
;PROXY_PROTOCOL_HEADER_TIMEOUT=5s
;;
; Accept PROXY protocol headers with UNKNOWN type
;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false
;;
;; Set the domain for the server
;DOMAIN = localhost ;DOMAIN = localhost
;; ;;
;; The AppURL is used to generate public URL links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/". ;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
;; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy. ;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
;ROOT_URL =
;;
;; Controls how to detect the public URL.
;; Although it defaults to "legacy" (to avoid breaking existing users), most instances should use the "auto" behavior,
;; especially when the Gitea instance needs to be accessed in a container network.
;; * legacy: detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL".
;; * auto: always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL".
;PUBLIC_URL_DETECTION = legacy
;; ;;
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy. ;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
;; DO NOT USE IT IN PRODUCTION!!! ;; DO NOT USE IT IN PRODUCTION!!!
@ -84,25 +89,13 @@ RUN_USER = ; git
;STATIC_URL_PREFIX = ;STATIC_URL_PREFIX =
;; ;;
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket. ;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
;; If PROTOCOL is set to "http+unix" or "fcgi+unix", this should be the name of the Unix socket file to use. ;; If PROTOCOL is set to `http+unix` or `fcgi+unix`, this should be the name of the Unix socket file to use.
;; Relative paths will be made absolute against the _`AppWorkPath`_. ;; Relative paths will be made absolute against the _`AppWorkPath`_.
;HTTP_ADDR = 0.0.0.0 ;HTTP_ADDR = 0.0.0.0
;; ;;
;; The port to listen on for "http" or "https" protocol. Leave empty when using a unix socket. ;; The port to listen on. Leave empty when using a unix socket.
;HTTP_PORT = 3000 ;HTTP_PORT = 3000
;; ;;
;; Expect PROXY protocol headers on connections
;USE_PROXY_PROTOCOL = false
;;
;; Use PROXY protocol in TLS Bridging mode
;PROXY_PROTOCOL_TLS_BRIDGING = false
;;
;; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
;PROXY_PROTOCOL_HEADER_TIMEOUT = 5s
;;
;; Accept PROXY protocol headers with UNKNOWN type
;PROXY_PROTOCOL_ACCEPT_UNKNOWN = false
;;
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server ;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main ;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for ;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
@ -110,8 +103,8 @@ RUN_USER = ; git
;REDIRECT_OTHER_PORT = false ;REDIRECT_OTHER_PORT = false
;PORT_TO_REDIRECT = 80 ;PORT_TO_REDIRECT = 80
;; ;;
;; expect PROXY protocol header on connections to https redirector, defaults to USE_PROXY_PROTOCOL ;; expect PROXY protocol header on connections to https redirector.
;REDIRECTOR_USE_PROXY_PROTOCOL = ;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
;; Minimum and maximum supported TLS versions ;; Minimum and maximum supported TLS versions
;SSL_MIN_VERSION=TLSv1.2 ;SSL_MIN_VERSION=TLSv1.2
;SSL_MAX_VERSION= ;SSL_MAX_VERSION=
@ -135,14 +128,13 @@ RUN_USER = ; git
;; most cases you do not need to change the default value. Alter it only if ;; most cases you do not need to change the default value. Alter it only if
;; your SSH server node is not the same as HTTP node. For different protocol, the default ;; your SSH server node is not the same as HTTP node. For different protocol, the default
;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`. ;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`.
;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`. ;; If `PROTOCOL` is `fcgi` or `fcgi+unix`, the default value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
;; If listen on `0.0.0.0`, the default value is `{PROTOCOL}://localhost:{HTTP_PORT}/`. ;; If listen on `0.0.0.0`, the default value is `%(PROTOCOL)s://localhost:%(HTTP_PORT)s/`, Otherwise the default
;; Otherwise the default value is `{PROTOCOL}://{HTTP_ADDR}:{HTTP_PORT}/`. ;; value is `%(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/`.
;; Most users don't need (and shouldn't) set this value. ;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
;LOCAL_ROOT_URL =
;; ;;
;; When making local connections pass the PROXY protocol header, defaults to USE_PROXY_PROTOCOL ;; When making local connections pass the PROXY protocol header.
;LOCAL_USE_PROXY_PROTOCOL = ;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)s
;; ;;
;; Disable SSH feature when not available ;; Disable SSH feature when not available
;DISABLE_SSH = false ;DISABLE_SSH = false
@ -154,17 +146,13 @@ RUN_USER = ; git
;SSH_SERVER_USE_PROXY_PROTOCOL = false ;SSH_SERVER_USE_PROXY_PROTOCOL = false
;; ;;
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. ;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
;BUILTIN_SSH_SERVER_USER = ;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s
;; ;;
;; Domain name to be exposed in clone URL, defaults to DOMAIN or the domain part of ROOT_URL ;; Domain name to be exposed in clone URL
;SSH_DOMAIN = ;SSH_DOMAIN = %(DOMAIN)s
;; ;;
;; SSH username displayed in clone URLs. It defaults to BUILTIN_SSH_SERVER_USER or RUN_USER. ;; SSH username displayed in clone URLs.
;; If it is set to "(DOER_USERNAME)", it will use current signed-in user's username. ;SSH_USER = %(BUILTIN_SSH_SERVER_USER)s
;; This option is only for some advanced users who have configured their SSH reverse-proxy
;; and need to use different usernames for git SSH clone.
;; Most users should just leave it blank.
;SSH_USER =
;; ;;
;; The network interface the builtin SSH server should listen on ;; The network interface the builtin SSH server should listen on
;SSH_LISTEN_HOST = ;SSH_LISTEN_HOST =
@ -172,8 +160,8 @@ RUN_USER = ; git
;; Port number to be exposed in clone URL ;; Port number to be exposed in clone URL
;SSH_PORT = 22 ;SSH_PORT = 22
;; ;;
;; The port number the builtin SSH server should listen on, defaults to SSH_PORT ;; The port number the builtin SSH server should listen on
;SSH_LISTEN_PORT = ;SSH_LISTEN_PORT = %(SSH_PORT)s
;; ;;
;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. ;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
;SSH_ROOT_PATH = ;SSH_ROOT_PATH =
@ -186,19 +174,30 @@ RUN_USER = ; git
;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true
;; ;;
;; For the builtin SSH server, choose the supported ciphers/key-exchange-algorithms/MACs for SSH connections. ;; For the built-in SSH server, choose the ciphers to support for SSH connections,
;; The supported names are listed in https://github.com/golang/crypto/blob/master/ssh/common.go. ;; for system SSH this setting has no effect
;; Leave them empty to use the Golang crypto's recommended default values. ;SSH_SERVER_CIPHERS = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com
;; For system SSH (non-builtin SSH server), this setting has no effect. ;;
;SSH_SERVER_CIPHERS = ;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
;SSH_SERVER_KEY_EXCHANGES = ;; for system SSH this setting has no effect
;SSH_SERVER_MACS = ;SSH_SERVER_KEY_EXCHANGES = curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1
;;
;; For the built-in SSH server, choose the MACs to support for SSH connections,
;; for system SSH this setting has no effect
;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1
;; ;;
;; For the built-in SSH server, choose the keypair to offer as the host key ;; For the built-in SSH server, choose the keypair to offer as the host key
;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub ;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
;; relative paths are made absolute relative to the APP_DATA_PATH ;; relative paths are made absolute relative to the %(APP_DATA_PATH)s
;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa
;; ;;
;; Directory to create temporary files in when testing public keys using ssh-keygen,
;; default is the system temporary directory.
;SSH_KEY_TEST_PATH =
;;
;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
;SSH_KEYGEN_PATH =
;;
;; Enable SSH Authorized Key Backup when rewriting all keys, default is false ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false
;SSH_AUTHORIZED_KEYS_BACKUP = false ;SSH_AUTHORIZED_KEYS_BACKUP = false
;; ;;
@ -289,9 +288,6 @@ RUN_USER = ; git
;; Default path for App data ;; Default path for App data
;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_ ;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_
;; ;;
;; Base path for App's temp files, leave empty to use the managed tmp directory in APP_DATA_PATH
;APP_TEMP_PATH =
;;
;; Enable gzip compression for runtime-generated content, static resources excluded ;; Enable gzip compression for runtime-generated content, static resources excluded
;ENABLE_GZIP = false ;ENABLE_GZIP = false
;; ;;
@ -520,10 +516,6 @@ INTERNAL_TOKEN =
;; ;;
;; On user registration, record the IP address and user agent of the user to help identify potential abuse. ;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
;; RECORD_USER_SIGNUP_METADATA = false ;; RECORD_USER_SIGNUP_METADATA = false
;;
;; Set the two-factor auth behavior.
;; Set to "enforced", to force users to enroll into Two-Factor Authentication, users without 2FA have no access to repositories via API or web.
;TWO_FACTOR_AUTH =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -590,7 +582,7 @@ ENABLED = true
[log] [log]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Root path for the log files - defaults to "{AppWorkPath}/log" ;; Root path for the log files - defaults to %(GITEA_WORK_DIR)/log
;ROOT_PATH = ;ROOT_PATH =
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -690,8 +682,8 @@ LEVEL = Info
;; The path of git executable. If empty, Gitea searches through the PATH environment. ;; The path of git executable. If empty, Gitea searches through the PATH environment.
;PATH = ;PATH =
;; ;;
;; The HOME directory for Git, defaults to "{APP_DATA_PATH}/home" ;; The HOME directory for Git
;HOME_PATH = ;HOME_PATH = %(APP_DATA_PATH)s/home
;; ;;
;; Disables highlight of added and removed changes ;; Disables highlight of added and removed changes
;DISABLE_DIFF_HIGHLIGHT = false ;DISABLE_DIFF_HIGHLIGHT = false
@ -782,7 +774,7 @@ LEVEL = Info
;ALLOW_ONLY_EXTERNAL_REGISTRATION = false ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false
;; ;;
;; User must sign in to view anything. ;; User must sign in to view anything.
;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, ;; 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. ;; for example: block anonymous AI crawlers from accessing repo code pages.
;; The "expensive" mode is experimental and subject to change. ;; The "expensive" mode is experimental and subject to change.
;REQUIRE_SIGNIN_VIEW = false ;REQUIRE_SIGNIN_VIEW = false
@ -946,29 +938,7 @@ LEVEL = Info
;; ;;
;; Disable the code explore page. ;; Disable the code explore page.
;DISABLE_CODE_PAGE = false ;DISABLE_CODE_PAGE = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[qos]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Enable request quality of service and overload protection.
; ENABLED = false
;;
;; The maximum number of concurrent requests that the server will
;; process before enqueueing new requests. Default is "CpuNum * 4".
; MAX_INFLIGHT =
;;
;; The maximum number of requests that can be enqueued before new
;; requests will be dropped.
; MAX_WAITING = 100
;;
;; Target maximum wait time a request may be enqueued for. Requests
;; that are enqueued for less than this amount of time will not be
;; dropped. When wait times exceed this amount, a portion of requests
;; will be dropped until wait times have decreased below this amount.
; TARGET_WAIT_TIME = 250ms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -982,8 +952,8 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository] ;[repository]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Root path for storing all repository data. By default, it is set to "{APP_DATA_PATH}/gitea-repositories". ;; Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
;; A relative path is interpreted as "{AppWorkPath}/{ROOT}" (use AppWorkPath as base path). ;; A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
;ROOT = ;ROOT =
;; ;;
;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available. ;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available.
@ -1093,6 +1063,15 @@ LEVEL = Info
;; Separate extensions with a comma. To line wrap files without an extension, just put a comma ;; Separate extensions with a comma. To line wrap files without an extension, just put a comma
;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd, ;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd,
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository.local]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart)
;LOCAL_COPY_PATH = tmp/local-repo
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository.upload] ;[repository.upload]
@ -1102,6 +1081,9 @@ LEVEL = Info
;; Whether repository file uploads are enabled. Defaults to `true` ;; Whether repository file uploads are enabled. Defaults to `true`
;ENABLED = true ;ENABLED = true
;; ;;
;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart)
;TEMP_PATH = data/tmp/uploads
;;
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
;ALLOWED_TYPES = ;ALLOWED_TYPES =
;; ;;
@ -1144,9 +1126,6 @@ LEVEL = Info
;; In default merge messages only include approvers who are official ;; In default merge messages only include approvers who are official
;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true ;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true
;; ;;
;; In default squash-merge messages include the commit message of all commits comprising the pull request.
;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false
;;
;; Add co-authored-by and co-committed-by trailers if committer does not match author ;; Add co-authored-by and co-committed-by trailers if committer does not match author
;ADD_CO_COMMITTER_TRAILERS = true ;ADD_CO_COMMITTER_TRAILERS = true
;; ;;
@ -1155,10 +1134,6 @@ LEVEL = Info
;; ;;
;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo. ;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
;RETARGET_CHILDREN_ON_MERGE = true ;RETARGET_CHILDREN_ON_MERGE = true
;;
;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated.
;; Use "-1" to always check all pull requests (old behavior). Use "0" to always delay the checks.
;DELAY_CHECK_FOR_INACTIVE_DAYS = 7
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1186,24 +1161,17 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; GPG or SSH key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey ;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
;; Depending on the value of SIGNING_FORMAT this is either:
;; - openpgp: the GPG key ID
;; - ssh: the path to the ssh public key "/path/to/key.pub": where "/path/to/key" is the private key, use ssh-keygen -t ed25519 to generate a new key pair without password
;; run in the context of the RUN_USER ;; run in the context of the RUN_USER
;; Switch to none to stop signing completely ;; Switch to none to stop signing completely
;SIGNING_KEY = default ;SIGNING_KEY = default
;; ;;
;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer and the signing format. ;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer.
;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to ;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to
;; the results of git config --get user.name, git config --get user.email and git config --default openpgp --get gpg.format respectively and can only be overridden ;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden
;; by setting the SIGNING_KEY ID to the correct ID.) ;; by setting the SIGNING_KEY ID to the correct ID.)
;SIGNING_NAME = ;SIGNING_NAME =
;SIGNING_EMAIL = ;SIGNING_EMAIL =
;; SIGNING_FORMAT can be one of:
;; - openpgp (default): use GPG to sign commits
;; - ssh: use SSH to sign commits
;SIGNING_FORMAT = openpgp
;; ;;
;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter ;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
;DEFAULT_TRUST_MODEL = collaborator ;DEFAULT_TRUST_MODEL = collaborator
@ -1230,13 +1198,6 @@ LEVEL = Info
;; - commitssigned: require that all the commits in the head branch are signed. ;; - commitssigned: require that all the commits in the head branch are signed.
;; - approved: only sign when merging an approved pr to a protected branch ;; - approved: only sign when merging an approved pr to a protected branch
;MERGES = pubkey, twofa, basesigned, commitssigned ;MERGES = pubkey, twofa, basesigned, commitssigned
;;
;; Determines which additional ssh keys are trusted for all signed commits regardless of the user
;; This is useful for ssh signing key rotation.
;; Exposes the provided SIGNING_NAME and SIGNING_EMAIL as the signer, regardless of the SIGNING_FORMAT value.
;; Multiple keys should be comma separated.
;; E.g."ssh-<algorithm> <key>". or "ssh-<algorithm> <key1>, ssh-<algorithm> <key2>".
;TRUSTED_SSH_KEYS =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1327,9 +1288,6 @@ LEVEL = Info
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css" ;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
;THEMES = ;THEMES =
;; ;;
;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future.
;FILE_ICON_THEME = material
;;
;; All available reactions users can choose on issues/prs and comments. ;; All available reactions users can choose on issues/prs and comments.
;; Values can be emoji alias (:smile:) or a unicode emoji. ;; Values can be emoji alias (:smile:) or a unicode emoji.
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png ;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
@ -1387,9 +1345,6 @@ LEVEL = Info
;; Number of repos that are displayed on one page ;; Number of repos that are displayed on one page
;REPO_PAGING_NUM = 15 ;REPO_PAGING_NUM = 15
;; Number of orgs that are displayed on profile page
;ORG_PAGING_NUM = 15
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[ui.meta] ;[ui.meta]
@ -1443,14 +1398,14 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Customize render options for different contexts. Set to "none" to disable the defaults, or use comma separated list: ;; Render soft line breaks as hard line breaks, which means a single newline character between
;; * short-issue-pattern: recognized "#123" issue reference and render it as a link to the issue ;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not
;; * new-line-hard-break: render soft line breaks as hard line breaks, which means a single newline character between ;; necessary to force a line break.
;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not ;; Render soft line breaks as hard line breaks for comments
;; necessary to force a line break. ;ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true
;RENDER_OPTIONS_COMMENT = short-issue-pattern, new-line-hard-break ;;
;RENDER_OPTIONS_WIKI = short-issue-pattern ;; Render soft line breaks as hard line breaks for markdown documents
;RENDER_OPTIONS_REPO_FILE = ;ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false
;; ;;
;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown ;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown
;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes) ;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes)
@ -1464,11 +1419,6 @@ LEVEL = Info
;; ;;
;; Enables math inline and block detection ;; Enables math inline and block detection
;ENABLE_MATH = true ;ENABLE_MATH = true
;;
;; Enable delimiters for math code block detection. Set to "none" to disable all,
;; or use comma separated list: inline-dollar, inline-parentheses, block-dollar, block-square-brackets
;; Defaults to "inline-dollar,block-dollar" to follow GitHub's behavior.
;MATH_CODE_BLOCK_DETECTION =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1559,8 +1509,7 @@ LEVEL = Info
;TYPE = persistable-channel ;TYPE = persistable-channel
;; ;;
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. ;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
;; Relative paths will be made absolute against "APP_DATA_PATH" ;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`.
;DATADIR = queues/
;; ;;
;; Default queue length before a channel queue will block ;; Default queue length before a channel queue will block
;LENGTH = 100000 ;LENGTH = 100000
@ -1808,9 +1757,6 @@ LEVEL = Info
;; ;;
;; convert \r\n to \n for Sendmail ;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true ;SENDMAIL_CONVERT_CRLF = true
;;
;; convert links of attached images to inline images. Only for images hosted in this gitea instance.
;EMBED_ATTACHMENT_IMAGES = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2462,8 +2408,6 @@ LEVEL = Info
;DEFAULT_GIT_TREES_PER_PAGE = 1000 ;DEFAULT_GIT_TREES_PER_PAGE = 1000
;; Default max size of a blob returned by the blobs API (default is 10MiB) ;; Default max size of a blob returned by the blobs API (default is 10MiB)
;DEFAULT_MAX_BLOB_SIZE = 10485760 ;DEFAULT_MAX_BLOB_SIZE = 10485760
;; Default max combined size of all blobs returned by the files API (default is 100MiB)
;DEFAULT_MAX_RESPONSE_SIZE = 104857600
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2504,7 +2448,7 @@ LEVEL = Info
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits) ;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
;MERMAID_MAX_SOURCE_CHARACTERS = 50000 ;MERMAID_MAX_SOURCE_CHARACTERS = 5000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2625,6 +2569,9 @@ LEVEL = Info
;; Currently, only `minio` and `azureblob` is supported. ;; Currently, only `minio` and `azureblob` is supported.
;SERVE_DIRECT = false ;SERVE_DIRECT = false
;; ;;
;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload`
;CHUNKED_UPLOAD_PATH = tmp/package-upload
;;
;; Maximum count of package versions a single owner can have (`-1` means no limits) ;; Maximum count of package versions a single owner can have (`-1` means no limits)
;LIMIT_TOTAL_OWNER_COUNT = -1 ;LIMIT_TOTAL_OWNER_COUNT = -1
;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)

12
flake.lock generated
View file

@ -5,11 +5,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1726560853,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1747179050, "lastModified": 1731139594,
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=", "narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e", "rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -29,14 +29,9 @@
poetry poetry
# backend # backend
go_1_24
gofumpt gofumpt
sqlite sqlite
]; ];
shellHook = ''
export GO="${pkgs.go_1_24}/bin/go"
export GOROOT="${pkgs.go_1_24}/share/go"
'';
}; };
} }
); );

257
go.mod
View file

@ -1,6 +1,6 @@
module code.gitea.io/gitea module code.gitea.io/gitea
go 1.24.4 go 1.23.8
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // 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: // But some CAs use negative serial number, just relax the check. related:
@ -8,11 +8,11 @@ go 1.24.4
godebug x509negativeserial=1 godebug x509negativeserial=1
require ( require (
code.gitea.io/actions-proto-go v0.4.1 code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.21.0 code.gitea.io/sdk/gitea v0.19.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.18.1 connectrpc.com/connect v1.17.0
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
gitea.com/go-chi/cache v0.2.1 gitea.com/go-chi/cache v0.2.1
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
@ -21,20 +21,19 @@ require (
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/httpsig v1.2.2 github.com/42wim/httpsig v1.2.2
github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815 github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.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/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.2.0 github.com/ProtonMail/go-crypto v1.1.6
github.com/PuerkitoBio/goquery v1.10.3 github.com/PuerkitoBio/goquery v1.10.0
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
github.com/alecthomas/chroma/v2 v2.17.0 github.com/alecthomas/chroma/v2 v2.15.0
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 github.com/aws/aws-sdk-go-v2/credentials v1.17.42
github.com/aws/aws-sdk-go-v2/service/codecommit v1.28.2 github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.0 github.com/blevesearch/bleve/v2 v2.4.2
github.com/bohde/codel v0.2.0 github.com/buildkite/terminal-to-html/v3 v3.16.3
github.com/buildkite/terminal-to-html/v3 v3.16.8 github.com/caddyserver/certmagic v0.21.4
github.com/caddyserver/certmagic v0.23.0
github.com/charmbracelet/git-lfs-transfer v0.2.0 github.com/charmbracelet/git-lfs-transfer v0.2.0
github.com/chi-middleware/proxy v1.1.1 github.com/chi-middleware/proxy v1.1.1
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
@ -42,35 +41,37 @@ require (
github.com/djherbis/nio/v3 v3.0.1 github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/editorconfig/editorconfig-core-go/v2 v2.6.2
github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap v1.2.1
github.com/emirpasic/gods v1.18.1 github.com/emirpasic/gods v1.18.1
github.com/ethantkoenig/rupture v1.0.1 github.com/ethantkoenig/rupture v1.0.1
github.com/felixge/fgprof v0.9.5 github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20250409143848-7113328b1f3d github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-enry/go-enry/v2 v2.9.1
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.6.1
github.com/go-git/go-git/v5 v5.16.0 github.com/go-git/go-git/v5 v5.13.1
github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-redsync/redsync/v4 v4.13.0 github.com/go-redsync/redsync/v4 v4.13.0
github.com/go-sql-driver/mysql v1.9.2 github.com/go-sql-driver/mysql v1.8.1
github.com/go-webauthn/webauthn v0.12.3 github.com/go-swagger/go-swagger v0.31.0
github.com/go-webauthn/webauthn v0.11.2
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-github/v71 v71.0.0 github.com/google/go-github/v71 v71.0.0
github.com/google/licenseclassifier/v2 v2.0.0 github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416 github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0 github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/h2non/gock v1.2.0
github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0 github.com/huandu/xstrings v1.5.0
@ -78,179 +79,217 @@ require (
github.com/jhillyerd/enmime v1.3.0 github.com/jhillyerd/enmime v1.3.0
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.10 github.com/klauspost/cpuid/v2 v2.2.8
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/markbates/goth v1.81.0 github.com/markbates/goth v1.80.0
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.28 github.com/mattn/go-sqlite3 v1.14.24
github.com/meilisearch/meilisearch-go v0.31.0 github.com/meilisearch/meilisearch-go v0.29.1-0.20241106140435-0bf60fad690a
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/microsoft/go-mssqldb v1.8.0 github.com/microsoft/go-mssqldb v1.7.2
github.com/minio/minio-go/v7 v7.0.91 github.com/minio/minio-go/v7 v7.0.80
github.com/msteinert/pam v1.2.0 github.com/msteinert/pam v1.2.0
github.com/nektos/act v0.2.63 github.com/nektos/act v0.2.63
github.com/niklasfasching/go-org v1.8.0 github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32 github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.20.5
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.7.3 github.com/redis/go-redis/v9 v9.7.3
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/tstranex/u2f v1.0.0 github.com/tstranex/u2f v1.0.0
github.com/ulikunitz/xz v0.5.12 github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli-docs/v3 v3.0.0-alpha6 github.com/urfave/cli/v2 v2.27.5
github.com/urfave/cli/v3 v3.3.3 github.com/wneessen/go-mail v0.5.2
github.com/wneessen/go-mail v0.6.2 github.com/xanzy/go-gitlab v0.112.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/yohcop/openid-go v1.0.1 github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.10 github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.127.0 golang.org/x/crypto v0.36.0
golang.org/x/crypto v0.39.0 golang.org/x/image v0.21.0
golang.org/x/image v0.26.0 golang.org/x/net v0.38.0
golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.27.0
golang.org/x/oauth2 v0.29.0 golang.org/x/sync v0.12.0
golang.org/x/sync v0.15.0 golang.org/x/sys v0.31.0
golang.org/x/sys v0.33.0 golang.org/x/text v0.23.0
golang.org/x/text v0.26.0 golang.org/x/tools v0.29.0
google.golang.org/grpc v1.72.0 google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.36.6 google.golang.org/protobuf v1.35.1
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.6.0 mvdan.cc/xurls/v2 v2.5.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.13 xorm.io/builder v0.3.13
xorm.io/xorm v1.3.9 xorm.io/xorm v1.3.9
) )
require ( require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/DataDog/zstd v1.5.7 // indirect github.com/DataDog/zstd v1.5.6 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect github.com/RoaringBitmap/roaring v1.9.4 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect
github.com/aws/smithy-go v1.22.3 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect
github.com/aws/smithy-go v1.22.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/bits-and-blooms/bitset v1.14.3 // indirect
github.com/blevesearch/bleve_index_api v1.2.8 // indirect github.com/blevesearch/bleve_index_api v1.1.12 // indirect
github.com/blevesearch/geo v0.2.0 // indirect github.com/blevesearch/geo v0.1.20 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect github.com/blevesearch/go-faiss v1.0.23 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect github.com/blevesearch/vellum v1.0.10 // indirect
github.com/blevesearch/zapx/v11 v11.4.1 // indirect github.com/blevesearch/zapx/v11 v11.3.10 // indirect
github.com/blevesearch/zapx/v12 v12.4.1 // indirect github.com/blevesearch/zapx/v12 v12.3.10 // indirect
github.com/blevesearch/zapx/v13 v13.4.1 // indirect github.com/blevesearch/zapx/v13 v13.3.10 // indirect
github.com/blevesearch/zapx/v14 v14.4.1 // indirect github.com/blevesearch/zapx/v14 v14.3.10 // indirect
github.com/blevesearch/zapx/v15 v15.4.1 // indirect github.com/blevesearch/zapx/v15 v15.3.16 // indirect
github.com/blevesearch/zapx/v16 v16.2.3 // indirect github.com/blevesearch/zapx/v16 v16.1.7 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect github.com/boombuler/barcode v1.0.2 // indirect
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.5.0 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/gomemcached v0.3.2 // indirect
github.com/couchbase/goutils v0.1.2 // indirect github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
github.com/go-ap/errors v0.0.0-20250409143711-5686c11ae650 // indirect github.com/go-ap/errors v0.0.0-20240910140019-1e9d33cc1568 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-webauthn/x v0.1.20 // indirect github.com/go-openapi/analysis v0.23.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/go-openapi/errors v0.22.0 // indirect
github.com/go-openapi/inflect v0.21.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/runtime v0.28.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
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.2 // 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/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect github.com/google/go-tpm v0.9.1 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jessevdk/go-flags v1.6.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mholt/acmez/v2 v2.0.3 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/miekg/dns v1.1.62 // indirect
github.com/miekg/dns v1.1.65 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mmcloughlin/avo v0.6.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect github.com/nwaples/rardecode v1.1.3 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rhysd/actionlint v1.7.7 // indirect github.com/rhysd/actionlint v1.7.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/toqueteos/webbrowser v1.2.0 // indirect
github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/com v1.0.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
@ -258,25 +297,27 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect
go.mongodb.org/mongo-driver v1.17.1 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/time v0.7.0 // indirect
golang.org/x/time v0.11.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => gitea.com/gitea/act v0.261.3
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why // TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0 replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0

564
go.sum

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@ import (
_ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/markdown"
_ "code.gitea.io/gitea/modules/markup/orgmode" _ "code.gitea.io/gitea/modules/markup/orgmode"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v2"
) )
// these flags will be set by the build flags // these flags will be set by the build flags

View file

@ -30,25 +30,6 @@ const (
ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
) )
func (status ArtifactStatus) ToString() string {
switch status {
case ArtifactStatusUploadPending:
return "upload is not yet completed"
case ArtifactStatusUploadConfirmed:
return "upload is completed"
case ArtifactStatusUploadError:
return "upload failed"
case ArtifactStatusExpired:
return "expired"
case ArtifactStatusPendingDeletion:
return "pending deletion"
case ArtifactStatusDeleted:
return "deleted"
default:
return "unknown"
}
}
func init() { func init() {
db.RegisterModel(new(ActionArtifact)) db.RegisterModel(new(ActionArtifact))
} }
@ -67,7 +48,7 @@ type ActionArtifact struct {
ContentEncoding string // The content encoding of the artifact ContentEncoding string // The content encoding of the artifact
ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it
ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it
Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete Status int64 `xorm:"index"` // The status of the artifact, uploading, expired or need-delete
CreatedUnix timeutil.TimeStamp `xorm:"created"` CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` UpdatedUnix timeutil.TimeStamp `xorm:"updated index"`
ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired
@ -87,7 +68,7 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
RepoID: t.RepoID, RepoID: t.RepoID,
OwnerID: t.OwnerID, OwnerID: t.OwnerID,
CommitSHA: t.CommitSHA, CommitSHA: t.CommitSHA,
Status: ArtifactStatusUploadPending, Status: int64(ArtifactStatusUploadPending),
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays), ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
} }
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
@ -127,11 +108,10 @@ func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) erro
type FindArtifactsOptions struct { type FindArtifactsOptions struct {
db.ListOptions db.ListOptions
RepoID int64 RepoID int64
RunID int64 RunID int64
ArtifactName string ArtifactName string
Status int Status int
FinalizedArtifactsV4 bool
} }
func (opts FindArtifactsOptions) ToOrders() string { func (opts FindArtifactsOptions) ToOrders() string {
@ -154,10 +134,6 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond {
if opts.Status > 0 { if opts.Status > 0 {
cond = cond.And(builder.Eq{"status": opts.Status}) cond = cond.And(builder.Eq{"status": opts.Status})
} }
if opts.FinalizedArtifactsV4 {
cond = cond.And(builder.Eq{"status": ArtifactStatusUploadConfirmed}.Or(builder.Eq{"status": ArtifactStatusExpired}))
cond = cond.And(builder.Eq{"content_encoding": "application/zip"})
}
return cond return cond
} }
@ -196,18 +172,18 @@ func ListPendingDeleteArtifacts(ctx context.Context, limit int) ([]*ActionArtifa
// SetArtifactExpired sets an artifact to expired // SetArtifactExpired sets an artifact to expired
func SetArtifactExpired(ctx context.Context, artifactID int64) error { func SetArtifactExpired(ctx context.Context, artifactID int64) error {
_, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusExpired}) _, err := db.GetEngine(ctx).Where("id=? AND status = ?", artifactID, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusExpired)})
return err return err
} }
// SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it // SetArtifactNeedDelete sets an artifact to need-delete, cron job will delete it
func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error { func SetArtifactNeedDelete(ctx context.Context, runID int64, name string) error {
_, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusPendingDeletion}) _, err := db.GetEngine(ctx).Where("run_id=? AND artifact_name=? AND status = ?", runID, name, ArtifactStatusUploadConfirmed).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusPendingDeletion)})
return err return err
} }
// SetArtifactDeleted sets an artifact to deleted // SetArtifactDeleted sets an artifact to deleted
func SetArtifactDeleted(ctx context.Context, artifactID int64) error { func SetArtifactDeleted(ctx context.Context, artifactID int64) error {
_, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: ArtifactStatusDeleted}) _, err := db.GetEngine(ctx).ID(artifactID).Cols("status").Update(&ActionArtifact{Status: int64(ArtifactStatusDeleted)})
return err return err
} }

View file

@ -5,7 +5,6 @@ package actions
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"slices" "slices"
"strings" "strings"
@ -16,7 +15,6 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -90,7 +88,7 @@ func (run *ActionRun) RefLink() string {
if refName.IsPull() { if refName.IsPull() {
return run.Repo.Link() + "/pulls/" + refName.ShortName() return run.Repo.Link() + "/pulls/" + refName.ShortName()
} }
return run.Repo.Link() + "/src/" + refName.RefWebLinkPath() return git.RefURL(run.Repo.Link(), run.Ref)
} }
// PrettyRef return #id for pull ref or ShortName for others // PrettyRef return #id for pull ref or ShortName for others
@ -156,7 +154,7 @@ func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) {
} }
func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) { func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, error) {
if run.Event.IsPullRequest() { if run.Event == webhook_module.HookEventPullRequest || run.Event == webhook_module.HookEventPullRequestSync {
var payload api.PullRequestPayload var payload api.PullRequestPayload
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
return nil, err return nil, err
@ -166,24 +164,12 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
return nil, fmt.Errorf("event %s is not a pull request event", run.Event) return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
} }
func (run *ActionRun) GetWorkflowRunEventPayload() (*api.WorkflowRunPayload, error) {
if run.Event == webhook_module.HookEventWorkflowRun {
var payload api.WorkflowRunPayload
if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil {
return nil, err
}
return &payload, nil
}
return nil, fmt.Errorf("event %s is not a workflow run event", run.Event)
}
func (run *ActionRun) IsSchedule() bool { func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0 return run.ScheduleID > 0
} }
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID). _, err := db.GetEngine(ctx).ID(repo.ID).
NoAutoTime().
SetExpr("num_action_runs", SetExpr("num_action_runs",
builder.Select("count(*)").From("action_run"). builder.Select("count(*)").From("action_run").
Where(builder.Eq{"repo_id": repo.ID}), Where(builder.Eq{"repo_id": repo.ID}),
@ -259,7 +245,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again. // If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
if n == 0 { if n == 0 {
return cancelledJobs, errors.New("job has changed, try again") return cancelledJobs, fmt.Errorf("job has changed, try again")
} }
cancelledJobs = append(cancelledJobs, job) cancelledJobs = append(cancelledJobs, job)
@ -293,7 +279,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return err return err
} }
run.Index = index run.Index = index
run.Title = util.EllipsisDisplayString(run.Title, 255) run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
if err := db.Insert(ctx, run); err != nil { if err := db.Insert(ctx, run); err != nil {
return err return err
@ -326,7 +312,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
} else { } else {
hasWaiting = true hasWaiting = true
} }
job.Name = util.EllipsisDisplayString(job.Name, 255) job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{ runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID, RunID: run.ID,
RepoID: run.RepoID, RepoID: run.RepoID,
@ -355,13 +341,13 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return committer.Commit() return committer.Commit()
} }
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) { func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
var run ActionRun var run ActionRun
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run) has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
return nil, fmt.Errorf("run with id %d: %w", runID, util.ErrNotExist) return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
} }
return &run, nil return &run, nil
@ -420,22 +406,29 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
if len(cols) > 0 { if len(cols) > 0 {
sess.Cols(cols...) sess.Cols(cols...)
} }
run.Title = util.EllipsisDisplayString(run.Title, 255) run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
affected, err := sess.Update(run) affected, err := sess.Update(run)
if err != nil { if err != nil {
return err return err
} }
if affected == 0 { if affected == 0 {
return errors.New("run has changed") return fmt.Errorf("run has changed")
// It's impossible that the run is not found, since Gitea never deletes runs. // It's impossible that the run is not found, since Gitea never deletes runs.
} }
if run.Status != 0 || slices.Contains(cols, "status") { if run.Status != 0 || slices.Contains(cols, "status") {
if run.RepoID == 0 { if run.RepoID == 0 {
setting.PanicInDevOrTesting("RepoID should not be 0") run, err = GetRunByID(ctx, run.ID)
if err != nil {
return err
}
} }
if err = run.LoadRepo(ctx); err != nil { if run.Repo == nil {
return err repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
if err != nil {
return err
}
run.Repo = repo
} }
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil { if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
return err return err

View file

@ -10,7 +10,6 @@ import (
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -20,12 +19,11 @@ import (
// ActionRunJob represents a job of a run // ActionRunJob represents a job of a run
type ActionRunJob struct { type ActionRunJob struct {
ID int64 ID int64
RunID int64 `xorm:"index"` RunID int64 `xorm:"index"`
Run *ActionRun `xorm:"-"` Run *ActionRun `xorm:"-"`
RepoID int64 `xorm:"index"` RepoID int64 `xorm:"index"`
Repo *repo_model.Repository `xorm:"-"` OwnerID int64 `xorm:"index"`
OwnerID int64 `xorm:"index"` CommitSHA string `xorm:"index"`
CommitSHA string `xorm:"index"`
IsForkPullRequest bool IsForkPullRequest bool
Name string `xorm:"VARCHAR(255)"` Name string `xorm:"VARCHAR(255)"`
Attempt int64 Attempt int64
@ -51,7 +49,7 @@ func (job *ActionRunJob) Duration() time.Duration {
func (job *ActionRunJob) LoadRun(ctx context.Context) error { func (job *ActionRunJob) LoadRun(ctx context.Context) error {
if job.Run == nil { if job.Run == nil {
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID) run, err := GetRunByID(ctx, job.RunID)
if err != nil { if err != nil {
return err return err
} }
@ -60,17 +58,6 @@ func (job *ActionRunJob) LoadRun(ctx context.Context) error {
return nil return nil
} }
func (job *ActionRunJob) LoadRepo(ctx context.Context) error {
if job.Repo == nil {
repo, err := repo_model.GetRepositoryByID(ctx, job.RepoID)
if err != nil {
return err
}
job.Repo = repo
}
return nil
}
// LoadAttributes load Run if not loaded // LoadAttributes load Run if not loaded
func (job *ActionRunJob) LoadAttributes(ctx context.Context) error { func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
if job == nil { if job == nil {
@ -96,7 +83,7 @@ func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
return &job, nil return &job, nil
} }
func GetRunJobsByRunID(ctx context.Context, runID int64) (ActionJobList, error) { func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*ActionRunJob, error) {
var jobs []*ActionRunJob var jobs []*ActionRunJob
if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil { if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil {
return nil, err return nil, err
@ -142,7 +129,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
{ {
// Other goroutines may aggregate the status of the run and update it too. // Other goroutines may aggregate the status of the run and update it too.
// So we need load the run and its jobs before updating the run. // So we need load the run and its jobs before updating the run.
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID) run, err := GetRunByID(ctx, job.RunID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -185,10 +172,10 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
return StatusSuccess return StatusSuccess
case hasCancelled: case hasCancelled:
return StatusCancelled return StatusCancelled
case hasRunning:
return StatusRunning
case hasFailure: case hasFailure:
return StatusFailure return StatusFailure
case hasRunning:
return StatusRunning
case hasWaiting: case hasWaiting:
return StatusWaiting return StatusWaiting
case hasBlocked: case hasBlocked:

View file

@ -7,7 +7,6 @@ import (
"context" "context"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -22,33 +21,7 @@ func (jobs ActionJobList) GetRunIDs() []int64 {
}) })
} }
func (jobs ActionJobList) LoadRepos(ctx context.Context) error {
repoIDs := container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
return j.RepoID, j.RepoID != 0 && j.Repo == nil
})
if len(repoIDs) == 0 {
return nil
}
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
if err := db.GetEngine(ctx).In("id", repoIDs).Find(&repos); err != nil {
return err
}
for _, j := range jobs {
if j.RepoID > 0 && j.Repo == nil {
j.Repo = repos[j.RepoID]
}
}
return nil
}
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error { func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
if withRepo {
if err := jobs.LoadRepos(ctx); err != nil {
return err
}
}
runIDs := jobs.GetRunIDs() runIDs := jobs.GetRunIDs()
runs := make(map[int64]*ActionRun, len(runIDs)) runs := make(map[int64]*ActionRun, len(runIDs))
if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil { if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil {
@ -57,9 +30,15 @@ func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
for _, j := range jobs { for _, j := range jobs {
if j.RunID > 0 && j.Run == nil { if j.RunID > 0 && j.Run == nil {
j.Run = runs[j.RunID] j.Run = runs[j.RunID]
j.Run.Repo = j.Repo
} }
} }
if withRepo {
var runsList RunList = make([]*ActionRun, 0, len(runs))
for _, r := range runs {
runsList = append(runsList, r)
}
return runsList.LoadRepos(ctx)
}
return nil return nil
} }
@ -80,31 +59,22 @@ type FindRunJobOptions struct {
func (opts FindRunJobOptions) ToConds() builder.Cond { func (opts FindRunJobOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RunID > 0 { if opts.RunID > 0 {
cond = cond.And(builder.Eq{"`action_run_job`.run_id": opts.RunID}) cond = cond.And(builder.Eq{"run_id": opts.RunID})
} }
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"`action_run_job`.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
} }
if opts.CommitSHA != "" { if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"`action_run_job`.commit_sha": opts.CommitSHA}) cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
} }
if len(opts.Statuses) > 0 { if len(opts.Statuses) > 0 {
cond = cond.And(builder.In("`action_run_job`.status", opts.Statuses)) cond = cond.And(builder.In("status", opts.Statuses))
} }
if opts.UpdatedBefore > 0 { if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"`action_run_job`.updated": opts.UpdatedBefore}) cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
} }
return cond return cond
} }
func (opts FindRunJobOptions) ToJoins() []db.JoinFunc {
if opts.OwnerID > 0 {
return []db.JoinFunc{
func(sess db.Engine) error {
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
return nil
},
}
}
return nil
}

View file

@ -58,14 +58,14 @@ func TestAggregateJobStatus(t *testing.T) {
{[]Status{StatusCancelled, StatusRunning}, StatusCancelled}, {[]Status{StatusCancelled, StatusRunning}, StatusCancelled},
{[]Status{StatusCancelled, StatusBlocked}, StatusCancelled}, {[]Status{StatusCancelled, StatusBlocked}, StatusCancelled},
// failure with other status, usually fail fast, but "running" wins to match GitHub's behavior // failure with other status, fail fast
// another reason that we can't make "failure" wins over "running": it would cause a weird behavior that user cannot cancel a workflow or get current running workflows correctly by filter after a job fail. // Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast.
{[]Status{StatusFailure}, StatusFailure}, {[]Status{StatusFailure}, StatusFailure},
{[]Status{StatusFailure, StatusSuccess}, StatusFailure}, {[]Status{StatusFailure, StatusSuccess}, StatusFailure},
{[]Status{StatusFailure, StatusSkipped}, StatusFailure}, {[]Status{StatusFailure, StatusSkipped}, StatusFailure},
{[]Status{StatusFailure, StatusCancelled}, StatusCancelled}, {[]Status{StatusFailure, StatusCancelled}, StatusCancelled},
{[]Status{StatusFailure, StatusWaiting}, StatusFailure}, {[]Status{StatusFailure, StatusWaiting}, StatusFailure},
{[]Status{StatusFailure, StatusRunning}, StatusRunning}, {[]Status{StatusFailure, StatusRunning}, StatusFailure},
{[]Status{StatusFailure, StatusBlocked}, StatusFailure}, {[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status // skipped with other status

View file

@ -10,7 +10,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/translation"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"xorm.io/builder" "xorm.io/builder"
@ -72,50 +71,39 @@ type FindRunOptions struct {
TriggerEvent webhook_module.HookEventType TriggerEvent webhook_module.HookEventType
Approved bool // not util.OptionalBool, it works only when it's true Approved bool // not util.OptionalBool, it works only when it's true
Status []Status Status []Status
CommitSHA string
} }
func (opts FindRunOptions) ToConds() builder.Cond { func (opts FindRunOptions) ToConds() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"`action_run`.repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
} }
if opts.WorkflowID != "" { if opts.WorkflowID != "" {
cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID}) cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
} }
if opts.TriggerUserID > 0 { if opts.TriggerUserID > 0 {
cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID}) cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
} }
if opts.Approved { if opts.Approved {
cond = cond.And(builder.Gt{"`action_run`.approved_by": 0}) cond = cond.And(builder.Gt{"approved_by": 0})
} }
if len(opts.Status) > 0 { if len(opts.Status) > 0 {
cond = cond.And(builder.In("`action_run`.status", opts.Status)) cond = cond.And(builder.In("status", opts.Status))
} }
if opts.Ref != "" { if opts.Ref != "" {
cond = cond.And(builder.Eq{"`action_run`.ref": opts.Ref}) cond = cond.And(builder.Eq{"ref": opts.Ref})
} }
if opts.TriggerEvent != "" { if opts.TriggerEvent != "" {
cond = cond.And(builder.Eq{"`action_run`.trigger_event": opts.TriggerEvent}) cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent})
}
if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"`action_run`.commit_sha": opts.CommitSHA})
} }
return cond return cond
} }
func (opts FindRunOptions) ToJoins() []db.JoinFunc {
if opts.OwnerID > 0 {
return []db.JoinFunc{func(sess db.Engine) error {
sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID)
return nil
}}
}
return nil
}
func (opts FindRunOptions) ToOrders() string { func (opts FindRunOptions) ToOrders() string {
return "`action_run`.`id` DESC" return "`id` DESC"
} }
type StatusInfo struct { type StatusInfo struct {
@ -124,14 +112,14 @@ type StatusInfo struct {
} }
// GetStatusInfoList returns a slice of StatusInfo // GetStatusInfoList returns a slice of StatusInfo
func GetStatusInfoList(ctx context.Context, lang translation.Locale) []StatusInfo { func GetStatusInfoList(ctx context.Context) []StatusInfo {
// same as those in aggregateJobStatus // same as those in aggregateJobStatus
allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning} allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning}
statusInfoList := make([]StatusInfo, 0, 4) statusInfoList := make([]StatusInfo, 0, 4)
for _, s := range allStatus { for _, s := range allStatus {
statusInfoList = append(statusInfoList, StatusInfo{ statusInfoList = append(statusInfoList, StatusInfo{
Status: int(s), Status: int(s),
DisplayedStatus: s.LocaleString(lang), DisplayedStatus: s.String(),
}) })
} }
return statusInfoList return statusInfoList

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