Compare commits

..

203 commits

Author SHA1 Message Date
Giteabot
09814117e3
Add changelog for 1.20.2 (#26208) (#26217)
Backport #26208 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: techknowlogick <matti@mdranta.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-29 19:23:54 +08:00
Giteabot
499c5594c3
Fix allowed user types setting problem (#26200) (#26206)
Backport #26200 by @lunny

Fix #25951

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-29 14:42:40 +08:00
Giteabot
ecfbcced46
Prevent primary key update on migration (#26192) (#26199)
Backport #26192 by @KN4CK3R

Fixes #25918

The migration fails on MSSQL because xorm tries to update the primary
key column. xorm prevents this if the column is marked as auto
increment:

c622cdaf89/internal/statements/update.go (L38-L40)

I think it would be better if xorm would check for primary key columns
here because updating such columns is bad practice. It looks like if
that auto increment check should do the same.

fyi @lunny

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-28 11:08:11 +02:00
Giteabot
892e24aaf1
Calculate MAX_WORKERS default value by CPU number (#26177) (#26183) 2023-07-27 19:24:07 +08:00
Lunny Xiao
666038a06d
Fix bug when pushing to a pull request which enabled dismiss approval automatically (#25882) (#26158)
Fix #25858
Backport #25882 

The option `dissmiss stale approvals` was listed on protected branch but
never implemented. This PR fixes that.

<img width="1006" alt="图片"

src="60bfa968-4db7-4c24-b8be-2e5978f91bb9">

<img width="1021" alt="图片"

src="8dabc14d-2dfe-40c2-94ed-24fcbf6e0e8f">
2023-07-27 10:36:54 +08:00
Giteabot
54614767a2
Fix handling of plenty Nuget package versions (#26075) (#26173)
Backport #26075 by @KN4CK3R

Fixes #25953

- Do not load full version information (v3)
- Add pagination support (v2)

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-26 21:41:40 +00:00
Giteabot
e42c5afadb
Fix typos in Contributing.md (#26170) (#26172)
Co-authored-by: Niko Hoffrén <niko.hoffren@gmail.com>
2023-07-26 14:46:13 -05:00
Giteabot
72b55c8094
Update email-setup.en-us.md (#26068) (#26166)
Backport #26068 by @felixvictor

The setting `MAILER_TYPE` is deprecated.
According to the config cheat sheet, it should be `PROTOCOL`.

Co-authored-by: Felix Victor <felix.victor.na@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-26 10:55:27 -05:00
Giteabot
a12d036a68
Fix bugs in LFS meta garbage collection (#26122) (#26157)
Backport #26122 by @Zettat123

This PR

- Fix #26093. Replace `time.Time` with `timeutil.TimeStamp`
- Fix #26135. Add missing `xorm:"extends"` to `CountLFSMetaObject` for
LFS meta object query
- Add a unit test for LFS meta object garbage collection

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-07-26 11:53:15 +00:00
Giteabot
65d6bdf0be
Fix UI regression of asciinema player (#26159) (#26162)
Backport #26159 by @wolfogre

It was caused by updating `asciinema-player`, the upstream changed the
CSS class prefix:
`40505e479e`

<details>
<summary>Before:</summary>

<img width="1320" alt="image"
src="b91a2cf5-c1da-43d6-bac2-bc278728b11e">

</details>


<details>
<summary>After:</summary>

<img width="1311" alt="image"
src="c9872d25-e0bb-43d4-8b1e-d87c6b03c0a2">

</details>

Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-26 12:34:55 +02:00
Lunny Xiao
c598741f01
Display deprecated warning in admin panel pages as well as in the log file (#26094) (#26154)
backport #26094 
Temporily resolve #25915
Related #25994

This PR includes #26007 's changes but have a UI to prompt administrator
about the deprecated settings as well as the log or console warning.
Then users will have enough time to notice the problem and don't have
surprise like before.

<img width="1293" alt="图片"
src="c33355f0-1ea7-4fb3-ad43-cd23cd15391d">
2023-07-26 09:22:39 +00:00
Lunny Xiao
bc73e6a85c
Update xorm version (#26128) (#26150)
backport #26128 to fix some serious bug.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-26 07:40:23 +00:00
Giteabot
a8445e9320
Remove "misc" scope check from public API endpoints (#26134) (#26149)
Backport #26134 by @wxiaoguang

Fix #26035

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-26 05:32:41 +00:00
Giteabot
0f73be0ae3
Fix LFS object list style (#26133) (#26147)
Backport #26133 by @wxiaoguang

Close #26104 . Only a quick fix, the UI is not perfect.

Before:

<details>


![image](7b10d42d-8317-4d99-80f9-b6c5fe05c17e)


![image](b43f1242-61a0-45e3-98b7-aa74b29f3813)

</details>

After:

<details>


![image](a8d27f70-781d-4702-866f-a56df6dd6c0a)


![image](379274e7-c67b-4c11-9cee-28a298b4ff5a)

</details>

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-26 12:58:48 +08:00
John Olheiser
4033d95dbf
Docusaurus-ify 1.20 (#26052)
See https://github.com/go-gitea/gitea/pull/26051

---------

Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: JonRB <4564448+eeyrjmr@users.noreply.github.com>
2023-07-26 10:00:14 +08:00
Giteabot
43213b816d
Fix CLI allowing creation of access tokens with existing name (#26071) (#26144)
Backport #26071 by @yardenshoham

We are now:
- Making sure there is no existing access token with the same name
- Making sure the given scopes are valid (we already did this before but
now we have a message)

The logic is mostly taken from
a12a5f3652/routers/api/v1/user/app.go (L101-L123)

Closes #26044

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
2023-07-26 01:30:50 +00:00
Giteabot
a55924aaf4
Increase table cell horizontal padding (#26140) (#26142)
Backport #26140 by @silverwind

Extract from https://github.com/go-gitea/gitea/pull/26043, just the
padding increase.

Before and After (hard to notice, but it's there):
<img width="427" alt="Screenshot 2023-07-25 at 19 37 12"
src="9543dcda-eccb-4739-b7dd-06b076108ab4">
<img width="420" alt="Screenshot 2023-07-25 at 19 37 26"
src="0a9c3724-81a1-4c67-a13b-4b728a51fc3a">

Co-authored-by: silverwind <me@silverwind.io>
2023-07-26 00:42:23 +00:00
Giteabot
782b137682
Fix incorrect router logger (#26137) (#26143)
Backport #26137 by @wxiaoguang

A low-level mistake:

* `log.Info` is global `Info` function, which calls "default" logger
* `logger.Info` is the for router's logger

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-26 08:37:43 +08:00
Giteabot
5992365fc1
added ssh mirror workaround description (#26096) (#26136)
Backport #26096 by @thigg

related #1635 #18159

This will probably be obsolete at some point, but it should not break
anything and it may help some users

Co-authored-by: thigg <thigg@users.noreply.github.com>
2023-07-25 18:32:39 +02:00
Giteabot
3b518a3af5
Improve commit graph alignment and truncating (#26112) (#26127)
Backport #26112 by @wxiaoguang

Fix #26101



![image](7507d201-822e-4534-8b20-e659d56b1268)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-25 11:43:39 +00:00
Giteabot
08cdc0da3d
Fix wrong workflow status when rerun a job in an already finished workflow (#26119) (#26124)
Backport #26119 by @yp05327

Before:

![image](fb687592-b117-4cd5-b076-2ca5ca847ea4)
After:

![image](c9b0683e-e81d-410b-8c35-fbe54327fab4)

After workflow finished, if you rerun a single job, the workflow status
will become to `Running` which is not correct as no jobs are running in
this workflow.

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-25 12:22:58 +02:00
Giteabot
3e07c54be3
Fix escape problems in the branch selector (#25875) (#26103)
Backport #25875 by @yp05327

Fix #25865

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-25 06:14:18 +00:00
Giteabot
e2596b0a99
Avoid writing config file if not installed (#26107) (#26113)
Backport #26107 by @wxiaoguang

Just like others (oauth2 secret, internal token, etc), do not generate
if no install lock

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-25 13:31:50 +08:00
Giteabot
a424f6d4f8
Fix handling of Debian files with trailing slash (#26087) (#26098)
Backport #26087 by @KN4CK3R

Fixes #26022

- Fix handling of files with trailing slash
- Fix handling of duplicate package file errors
- Added test for both

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-24 14:56:13 +00:00
Giteabot
59713541b6
fix Missing 404 swagger response docs for /admin/users/{username} (#26086) (#26089)
Backport #26086 by @CaiCandong

close #26079

Co-authored-by: caicandong <50507092+CaiCandong@users.noreply.github.com>
2023-07-24 12:07:39 +02:00
Giteabot
8d9193680d
Use stderr as fallback if the log file can't be opened (#26074) (#26083)
Backport #26074 by @wxiaoguang

If the log file can't be opened, what should it do? panic/exit? ignore
logs? fallback to stderr?

It seems that "fallback to stderr" is slightly better than others ....
2023-07-24 05:58:16 +00:00
Giteabot
ab4fd9aa1f
Fix duplicated url prefix on issue context menu (#26066) (#26067)
Backport #26066 by @lunny

Fix #26060

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-23 10:27:34 +00:00
Giteabot
221b90d289
Add changelog for 1.20.1 (#26015) (#26056)
Backport #26015 by @delvh

Co-authored-by: delvh <dev.lh@web.de>
2023-07-22 11:18:59 +02:00
Giteabot
81f5a87eb4
Fix version in rpm repodata/primary.xml.gz (#26009) (#26048)
Co-authored-by: Peter Verraedt <peter.verraedt@gmail.com>
2023-07-21 20:06:51 +00:00
Giteabot
8b002b429d
Adding remaining enum for migration repo model type. (#26021) (#26034)
Backport #26021 by @puni9869

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

Adding remaining enum for migration repo model type.

Co-authored-by: puni9869 <80308335+puni9869@users.noreply.github.com>
2023-07-21 08:54:43 +02:00
Giteabot
dfd371a363
RPM Registry: Show zypper commands for SUSE based distros as well (#25981) (#26020)
Backport #25981 by @asdil12

After RPM is supported with https://github.com/go-gitea/gitea/pull/23380
let's show the user
how to add the repo and install the RPM via all common package managers.

Co-authored-by: Dominik Heidler <dominik@heidler.eu>
2023-07-20 21:15:47 -04:00
Giteabot
54a516e9da
Fix the route for pull-request's authors (#26016) (#26018)
Backport #26016 by @wxiaoguang

Close #25906



![image](e689f3e1-9a90-46c0-89f4-2d61394d34d3)


Succeeded logs:

```
[I] router: completed GET /root/test/issues/posters?&q=%20&_=1689853025011 for [::1]:59271, 200 OK in 127.7ms @ repo/issue.go:3505(repo.IssuePosters)


[I] router: completed GET /root/test/pulls/posters?&q=%20&_=1689853968204 for [::1]:59269, 200 OK in 94.3ms @ repo/issue.go:3509(repo.PullPosters)
```

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-20 13:38:12 +00:00
Giteabot
ac129d4b4c
Correctly refer to dev tags as nightly in the docker docs (#26004) (#26019)
Backport #26004 by @jolheiser

As title, `dev` tags are no longer used since we switched to `nightly`

Signed-off-by: jolheiser <john.olheiser@gmail.com>
2023-07-20 21:03:40 +08:00
Giteabot
4d5e3b9372
Fix env config parsing for "GITEA____APP_NAME" (#26001) (#26013)
Backport #26001 by @wxiaoguang

Regression of #24832 

Fix the bug and add a test for it

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-20 11:56:36 +02:00
Giteabot
1ba0baa030
Fix commit status color on dashboard repolist (#25993) (#25998)
Backport #25993 by @silverwind

Followup to https://github.com/go-gitea/gitea/pull/25935 which has
missed to change the icon on the repolist because the logic is not
shared with templates.

Co-authored-by: silverwind <me@silverwind.io>
2023-07-19 23:24:04 +00:00
Giteabot
28e8c691a6
avoid hard-coding height in language dropdown menu (#25986) (#25997)
Backport #25986 by @earl-warren

This commit removes the hard-coded height of 500px, using that as a
max-height instead. The height of items in the dropdown menu, assuming a
default font size of 16px, is 36px, so the old CSS would cause overly
large dropdown menus in instances where less than 14 languages are
offered.

Refs: https://codeberg.org/forgejo/forgejo/pulls/1000

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: rome-user <rome-user@noreply.codeberg.org>
2023-07-20 01:05:22 +02:00
Giteabot
f81a612eb1
parseScope with owner/repo always sets owner to zero (#25987) (#25989)
Backport #25987 by @earl-warren

Refs: https://codeberg.org/forgejo/forgejo/pulls/1001

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
2023-07-19 22:57:17 +08:00
Giteabot
ee47face12
Update path related documents (#25417) (#25982)
Backport #25417 by @wxiaoguang

Update WorkPath/WORK_PATH related documents, remove out-dated
information.

Remove "StaticRootPath" on the admin config display page, because few
end user really need it, it only causes misconfiguration.


![image](8095afa4-da76-436b-9e89-2a92c229c01d)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-19 09:48:54 +00:00
Giteabot
864bdd0ac8
Make pending commit status yellow again (#25935) (#25968)
Backport #25935 by @silverwind

With the introduction of Actions, the pending commit icon has changed
from yellow to grey for Drone integrations which never set the "running"
status, so it stays in "pending" until completion.

I find it better to have this icon colored like on 1.19. Now both the
"pending" and "running" icons look the same, but I guess we could add an
animation to the "running" state similar to GitHub has to it later.

Before:
<img width="339" alt="Screenshot 2023-07-17 at 19 14 19"
src="2f4886e4-74fd-42ea-b59e-9af8f141bf1f">

After:
<img width="335" alt="Screenshot 2023-07-17 at 19 14 30"
src="53189642-e72d-47f6-9cbe-f14eda28f730">

Also, it matches GH's icon:

<img width="466" alt="image"
src="5804ff90-d223-4a3c-8093-7a9abbaacf87">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
2023-07-19 06:43:41 +00:00
Giteabot
037a3f0d8c
Show the mismatched ROOT_URL warning on the sign-in page if OAuth2 is enabled (#25947) (#25972)
Backport #25947 by @wolfogre

Since OAuth2 will callback the root URL, if the user starts signing in
from a wrong host, Gitea will return 500 because it cannot find the
session.

<details>
<summary>How to reproduce</summary>

<img width="901" alt="image"
src="2c2e255c-e13e-4a11-9be7-b226bee54920">

<img width="1014" alt="image"
src="b31cfcf6-a320-483d-9ce5-ba8562f065e1">

</details>


So show the mismatched ROOT_URL warning on the sign-in page if OAuth2 is
enabled.

<img width="1015" alt="image"
src="99e80b17-c790-49a3-bbf2-2bd9396a7daa">

Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-19 04:35:16 +00:00
Giteabot
227c3b67e0
Fix SSPI auth panic (#25955) (#25969)
Backport #25955 by @wxiaoguang

Try to fix #25952

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-18 22:28:06 +00:00
wxiaoguang
5c3662b902
Avoid creating directories when loading config (#25944) (#25957)
Backport #25944

The "creating dir/file during load config" is a longstanding and complex
problem.

This PR only does a quick patch, it still needs more refactorings in the
future.

Fix #25938
2023-07-18 20:24:07 +00:00
KN4CK3R
ab54310731
Disallow dangerous URL schemes (#25960) (#25964)
Regression: https://github.com/go-gitea/gitea/pull/24805
Closes: #25945

- Disallow `javascript`, `vbscript` and `data` (data uri images still
work) url schemes even if all other schemes are allowed
- Fixed older `cbthunderlink` tests

---------

Co-authored-by: delvh <dev.lh@web.de>
2023-07-18 19:48:52 +00:00
Giteabot
b7d054e4b5
Ignore runs-on with expressions when warning no matched runners (#25917) (#25933)
Backport #25917 by @wolfogre

Fix #25905

Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-18 06:40:28 +00:00
Giteabot
d032500687
Make environment-to-ini work with INSTALL_LOCK=true (#25926) (#25937)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
fix #25924
2023-07-17 18:28:54 +00:00
Giteabot
9159964ada
Avoid opening/closing PRs which are already merged (#25883) (#25903)
Backport #25883 by @yp05327

We can select PRs to open/close them by one click, but we forgot to
check whether it is merged.
You can get an opening merged PR:

![image](22c2e747-4bb9-4742-a9aa-ef39d5308bc5)

You can confirm this in:
https://try.gitea.io/yp05327/testrepo/pulls/5

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-17 12:14:22 +00:00
Giteabot
9369b38315
Skip unuseful error message in dev mode when watching local filesystem (#25919) (#25927)
Backport #25919 by @wxiaoguang

Before, in dev mode, there might be some error logs like:

```
2023/07/17 13:54:51 ...s/assetfs/layered.go:221:WatchLocalChanges() [E] Unable to watch directory .: lstat /data/work/gitea/custom/templates: no such file or directory

```

Because there is no "custom/templates" directory.

After: ignore such error, no such error message anymore.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-17 10:26:29 +00:00
Giteabot
6e82d0bb7c
Add shutting down notice (#25920) (#25922)
Backport #25920 by @KN4CK3R

Got the same problem as #25915 when updating an instance. The
`log.Fatal` should have been marked as breaking in #23911.

This PR adds a notice that the system is shutting down because of the
deprecated setting.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-17 09:44:10 +00:00
Giteabot
36b9a86bd8
Fix incorrect milestone count when provide a keyword (#25880) (#25904)
Backport #25880 by @yp05327

You can confirm this issue in:
https://try.gitea.io/yp05327/testrepo/milestones?state=open&q=a
There's no milestone, but the count is 1.

![image](25e58cee-aeeb-43c1-8ec8-6e2ec6bf1284)

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-17 11:12:59 +02:00
delvh
e627f161c2
Add 1.20.0 changelog (#25901) (#25910)
Backport of #25901
2023-07-16 21:08:53 +02:00
Giteabot
de8127e78b
fix incorrect repo url when changed the case of ownername (#25733) (#25881)
Backport #25733 by @hiifong

When only the casing of the username changes, update the owner name of the repo,
and keep the original logic consistent with other conditions.

example: assume your username is `gitea`, lowercase username is `gitea` too,
repo URL is `.../gitea/{repo}`.
You change your username to `Gitea`, `GiTea` or something like that,
as long as the lowercase username is still `gitea`, the repo URL remained `.../gitea/{repo}`.

this pr keeps the new username consistent with the repo URL.

Before:

![image](84177296-f0ff-4176-84f1-1f9ec3f5b86f)
![image](8f8f4a12-ecdd-4dec-af89-85c009b0ccfe)

After: 

![image](0564edb6-9467-405a-8cd4-d6f70e6f614b)
![image](554ecd6e-e5a1-43bc-a46d-99e988c2ff58)

Co-authored-by: hiifong <i@hiif.ong>
2023-07-15 19:47:24 +02:00
Denys Konovalov
f7e271ff85
Backport locales to v1.20 (#25899) 2023-07-15 11:26:40 -04:00
Giteabot
186f07bbf7
Make add line comment buttons focusable (#25894) (#25896)
Backport #25894 by @sebastian-sauer

Use a real button and add an aria-label.
Additionally, show the button whenever it is focused.
See https://codeberg.org/forgejo/forgejo/issues/998 for explanation.

Our handling of this button is now equal to that of GitHub.
Nothing has changed visually.

Co-authored-by: sebastian-sauer <sauer.sebastian@gmail.com>
2023-07-15 12:18:49 +02:00
Giteabot
45b1f4dd3b
Add support for different Maven POM encoding (#25873) (#25890)
Backport #25873 by @KN4CK3R

Fixes #25853

- Maven POM files aren't always UTF-8 encoded.
- Reject the upload of unparsable POM files

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-14 10:27:15 +00:00
Giteabot
026e745b9e
Fix incorrect release count (#25879) (#25887)
Backport #25879 by @yp05327

Release count is not correct:
https://try.gitea.io/yp05327/testrepo/tags

![image](07f97c62-d450-4ccb-b3f2-3e0af9d9fc52)

https://try.gitea.io/yp05327/testrepo/releases

![image](6f1d55a4-bb68-445d-84b9-90552a40f403)

https://try.gitea.io/yp05327/testrepo/releases/tag/testtag

![image](09ab5d51-52b6-4621-a571-3100198eb260)

We already have correct release count, no need to calculate it again.

c5e187c389/modules/context/repo.go (L547)

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-14 09:32:43 +00:00
Giteabot
c334be8284
Fix empty project displayed in issue sidebar (#25802) (#25854)
Backport #25802 by @yp05327

You can confirm this issue in
https://try.gitea.io/yp05327/testrepo/issues/2

Before:

![image](1ab476dc-2f9b-4c85-9e87-105fc73af1ee)
After:

![image](786f984d-5c27-4eff-b3d9-159f68034ce4)

This issue comes from the change in #25468.
`LoadProject` will always return at least one record, so we use
`ProjectID` to check whether an issue is linked to a project in the old
code.
As other `issue.LoadXXX` functions, we need to check the return value
from `xorm.Session.Get`.

In recent unit tests, we only test `issueList.LoadAttributes()` but
don't test `issue.LoadAttributes()`. So I added a new test for
`issue.LoadAttributes()` in this PR.

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Denys Konovalov <privat@denyskon.de>
2023-07-12 19:07:03 +02:00
Giteabot
353dcc5ad4
Fix the error message when the token is incorrect (#25701) (#25836)
Backport #25701 by @CaiCandong

we refactored `userIDFromToken` for the token parsing part into a new
function `parseToken`. `parseToken` returns the string `token` from
request, and a boolean `ok` representing whether the token exists or
not. So we can distinguish between token non-existence and token
inconsistency in the `verfity` function, thus solving the problem of no
proper error message when the token is inconsistent.
close #24439  
related #22119

Co-authored-by: caicandong <50507092+CaiCandong@users.noreply.github.com>
Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-12 10:18:27 +00:00
wxiaoguang
7811027ca1
Backport locale to 1.20 (#25840)
With manual fixes

Co-authored-by: Giteabot <teabot@gitea.io>
2023-07-12 11:43:57 +02:00
Giteabot
abe9c641ce
Show correct SSL Mode on "install page" (#25818) (#25838)
Backport #25818 by @wxiaoguang

Fix #25817


![image](49f7b85d-c229-41b5-86fd-58cd812eaca6)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-12 02:01:38 -04:00
Giteabot
052e65e63f
Fix incorrect oldest sort in project list (#25806) (#25835)
Backport #25806 by @yp05327

sort type `oldest` should be `Asc`.
Added a test for this.

I see we have `SearchOrderBy` in db model, but we are using many
different ways to define the sort type.
~Maybe we can improve this later.~
↑ Improved in this PR

Co-authored-by: yp05327 <576951401@qq.com>
2023-07-12 13:22:17 +08:00
Zettat123
c1a10be07e
Fix activity type match in matchPullRequestEvent (#25746) (#25796)
Backport #25746

Fix #25736
Caused by #24048

Right now we only check the activity type for `pull_request` event when
`types` is specified or there are no `types` and filter. If a workflow
only specifies filters but no `types` like this:
```
on:
  pull_request:
    branches: [main]
```
the workflow will be triggered even if the activity type is not one of
`[opened, reopened, sync]`. We need to check the activity type in this
case.
2023-07-11 06:42:07 +00:00
Giteabot
2b79d3fd52
For API attachments, use API URL (#25639) (#25814)
Backport #25639 by @lunny

Fix #25257

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-10 12:56:16 +00:00
wxiaoguang
b4460cf541
Make "install page" respect environment config (#25648) (#25799)
Backport #25648

Replace #25580

Fix #19453

The problem was: when users set "GITEA__XXX__YYY" , the "install page"
doesn't respect it.

So, to make the result consistent and avoid surprising end users, now
the "install page" also writes the environment variables to the config
file.

And, to make things clear, there are enough messages on the UI to tell
users what will happen.

There are some necessary/related changes to `environment-to-ini.go`:

* The "--clear" flag is removed and it was incorrectly written there.
The "clear" operation should be done if INSTALL_LOCK=true
* The "--prefix" flag is removed because it's never used, never
documented and it only causes inconsistent behavior.

The only conflict during backport is "ui divider" in
templates/install.tmpl
2023-07-10 11:51:05 +00:00
Giteabot
a1bc2aa05e
Avoid amending the Rebase and Fast-forward merge if there is no message template (#25779) (#25809)
Backport #25779 by @wxiaoguang

Related #22669. Close #25177

After the fix:


![image](0e900927-ea72-4f8f-bde6-5ed927cb02f4)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-10 10:23:54 +00:00
Giteabot
d713cf6150
Fix WORK_DIR for docker (root) image (#25738) (#25811)
Backport #25738 by @wxiaoguang

Fix #25726 

#17846 chose an incorrect WORK_DIR path for docker root image.

Gitea's work-path was already used as the base path for various paths
(like AppDataPath), so, the work-path should be mounted to a volume in a
docker image.

Now, for docker root image, it's unavoidable to mix the
WorkPath/CustomPath/AppDataPath in the same directory ("/data/gitea"),
because some of them have already been mixed.

Some directories in the screenshot are for "CustomPath" , while others
are for "AppDataPath", due to the technical debts in old code:

```
CUSTOM_PATH="/data/gitea"
APP_DATA_PATH = /data/gitea
```

<details>


![image](9f0648ac-f731-4a08-9f26-1af01a1824b1)

</details>


This PR is breaking but this is the only way at the moment to avoid
users losing their data accidently

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-10 10:54:16 +02:00
wxiaoguang
012b804a9a
Clarify "text-align" CSS helpers, fix clone button padding (#25763) (#25764)
Backport  #25763

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-07-10 00:19:24 +02:00
Giteabot
372b622c2b
Revert package access change from #23879 (#25707) (#25785)
Backport #25707 by @KN4CK3R

Fixes (?) #25538
Fixes https://codeberg.org/forgejo/forgejo/issues/972

Regression #23879

#23879 introduced a change which prevents read access to packages if a
user is not a member of an organization.

That PR also contained a change which disallows package access if the
team unit is configured with "no access" for packages. I don't think
this change makes sense (at the moment). It may be relevant for private
orgs. But for public or limited orgs that's useless because an
unauthorized user would have more access rights than the team member.
This PR restores the old behaviour "If a user has read access for an
owner, they can read packages".

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-09 21:00:42 +00:00
Giteabot
06bcdfe77a
Remove unused code (#25734) (#25788)
Backport #25734 by @KN4CK3R

The method is only used in the test. Found it because I changed the
fixtures and had a hard time fixing this test. My revenge is deleting
it.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-07-09 19:47:58 +00:00
Giteabot
a5a3c81412
Fix notification list bugs (#25781) (#25787)
Backport #25781 by @wxiaoguang

Fix #25627

1. `ctx.Data["Link"]` should use relative URL but not AppURL
2. The `data-params` is incorrect because it doesn't contain "page". JS
can simply use "window.location.search" to construct the AJAX URL
3. The `data-xxx` and `id` in notification_subscriptions.tmpl were
copied&pasted, they don't have affect.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-09 19:15:00 +00:00
Giteabot
ea2c9de3c4
Test if container blob is accessible before mounting (#22759) (#25784)
Backport #22759 by @KN4CK3R

related #16865

This PR adds an accessibility check before mounting container blobs.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
2023-07-09 12:00:04 +00:00
silverwind
348a6bf70d
Always pass 6-digit hex color to monaco (#25780) (#25782)
Backport https://github.com/go-gitea/gitea/pull/25780, clean
cherry-pick.

Monaco can not deal with color formats other than 6-digit hex, so we
convert the colors for it via new
[`tinycolor2`](https://github.com/bgrins/TinyColor) dependency (5kB
minzipped).

Also, with the addition of the module, we can replace the existing
`hexToRGBColor` usage, I verified it is compatible with the current
tests before removing the function.

Fixes: https://github.com/go-gitea/gitea/issues/25770
2023-07-09 13:06:13 +02:00
Giteabot
68a3961bf1
docs: rootless docker ssh's default port is 2222 (#25771) (#25772)
Backport #25771 by @leavesster

---

according `docker/rootless/usr/local/bin/docker-setup.sh` , in rootless
docker setup, ssh port is 2222.
and mysql database case should port same as PostgreSQL port

Co-authored-by: leavesster <11785335+leavesster@users.noreply.github.com>
2023-07-09 15:45:42 +08:00
Giteabot
91dadedddf
Translate untranslated string in issues list (#25759) (#25761)
Backport #25759 by @Maks1mS

Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2023-07-07 23:04:08 +00:00
Giteabot
32eaba1b40
Hide add file button for pull mirrors (#25748) (#25751)
Backport #25748 by @hiifong

I think hiding the add file button for mirror repositories that can keep
the ui clean.

Before:

![image](84ecf1a5-1a92-4bb1-b472-b4988a4441a9)

After:

![image](95382e73-286b-4114-9997-456ed77e07ca)

Co-authored-by: hiifong <i@hiif.ong>
2023-07-07 14:12:59 +00:00
wxiaoguang
582dcaa12e
Remove broken translations (#25737)
Some translations were just copied&pasted and they duplicated a lot.

Now, they are broken .....

To avoid blocking 1.20 release, as a quick fix, remove all of them, only
keep the en-US texts.
2023-07-07 09:10:21 +02:00
Denys Konovalov
917ca5ded9
Several fixes for mobile UI (#25634) (#25689)
Backport #25634 

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

<details>
<summary>Screenshots</summary>

![Bildschirmfoto vom 2023-07-02

20-47-34](a8a0bff6-9ae3-48f3-b008-00c196a3f8fd)
![Bildschirmfoto vom 2023-07-02

20-47-45](172a0021-af74-4690-aa67-0e66688ce733)
![Bildschirmfoto vom 2023-07-02

20-48-37](14572ebd-0106-4c8a-ba27-b6b631375ee6)
![Bildschirmfoto vom 2023-07-02

20-49-08](7c0ba3aa-1712-482c-aae9-13394dbdaf8a)
![Bildschirmfoto vom 2023-07-02

20-50-28](8bd68e26-099a-4abd-8817-16d52af13167)
![Bildschirmfoto vom 2023-07-02

20-51-46](3beab8c6-3747-4829-be50-bafaed11000c)
![Bildschirmfoto vom 2023-07-02

20-54-12](51f82ef3-a32c-4c27-9056-e8711ed469cc)

</details>

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-07-07 00:34:00 +02:00
Giteabot
e595dfeec7
Allow/fix review (approve/reject) of empty PRs (#25690) (#25732)
Backport #25690 by @sebastian-sauer

gitea allows to create empty PRs.

Currently when you need approvals for a merge, you have to manually add
/files to the url to get to the files tab to approve / reject the PR.

This PR allows to open the files tab via the normal tab / link and then
fixes the layout of the files tab.

**Screenshots:**

Before:

![image](b5082e5e-8c32-4412-993e-b854905e96d3)

After:

![image](1f5e056e-396f-4dfb-8d14-e17a2f6495d9)

Co-authored-by: sebastian-sauer <sauer.sebastian@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-07-06 19:34:41 +00:00
Giteabot
03cacf971e
Check ctx.Written() for GetActionIssue (#25698) (#25711)
Backport #25698 by @wolfogre

Fix #25697.

Just avoid panic, maybe there's another bug to trigger this case.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-06 21:04:26 +02:00
Giteabot
68e0c802f7
Show correct naming for 1 comment (#25704) (#25712)
Backport #25704 by @earl-warren

- Resolves https://codeberg.org/forgejo/forgejo/issues/948

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-07-06 16:04:13 +00:00
Giteabot
09668b2e2e
Correct permissions for .ssh and authorized_keys (#25721) (#25730)
Backport #25721 by @wolfogre

Set the correct permissions on the .ssh directory and authorized_keys
file, or sshd will refuse to use them and lead to clone/push/pull
failures.

It could happen when users have copied their data to a new volume and
changed the file permission by accident, and it would be very hard to
troubleshoot unless users know how to check the logs of sshd which is
started by s6.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-07-06 11:02:56 -04:00
Giteabot
04eea29ecb
Fix tags header and pretty format numbers (#25624) (#25694)
Backport #25624 by @lunny

This casused by #23465

Before

release disabled
<img width="1320" alt="图片"
src="190a1c81-daa5-41bc-91ac-c9a0bf629b5f">

release enabled
<img width="1320" alt="图片"
src="a0372c31-727c-4ee0-a6b9-30e502498d90">

After

release disabled
<img width="1304" alt="图片"
src="a747ea80-a3d9-4792-8f6d-e8955da78b9e">

release enabled
<img width="1290" alt="图片"
src="7c0bc43a-9149-4148-859d-35839aeb60ca">

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-05 07:08:16 +00:00
Giteabot
511be9fe6e
Fix position of org follow button (#25688) (#25692)
Backport #25688 by @silverwind

This has recently regressed it seems. Put it back into same position as
https://github.com/go-gitea/gitea/pull/24345.

Before:
<img width="1246" alt="image"
src="bb410c29-5539-4dad-8351-8da8470f7091">

After:
<img width="1236" alt="Screenshot 2023-07-04 at 21 19 13"
src="072e0e83-defd-484d-8861-33d73fa0e446">

Co-authored-by: silverwind <me@silverwind.io>
2023-07-05 08:31:12 +02:00
silverwind
24e64fe372
Replace interface{} with any (#25686) (#25687)
Same perl replacement as https://github.com/go-gitea/gitea/pull/25686
but for 1.20 to ease future backporting.
2023-07-04 23:41:32 -04:00
Giteabot
4e310133f9
Prevent duplicate image loading (#25675) (#25684)
Backport #25675 by @delvh

Regression of #25672.

Co-authored-by: delvh <dev.lh@web.de>
2023-07-04 14:49:39 +00:00
silverwind
491f36d32a
Actions list enhancements (#25601) (#25678)
Backport https://github.com/go-gitea/gitea/pull/25601 to 1.20.

Various small enhancements to the actions list. Before and after:

<img width="1264" alt="Screenshot 2023-06-30 at 00 11 40"
src="bb4162ee-cdcf-4a73-b05e-f9521562edbb">
<img width="1264" alt="Screenshot 2023-06-30 at 00 09 51"
src="52a70ea9-4bb3-406e-904b-0fdaafde9582">

Co-authored-by: Giteabot <teabot@gitea.io>
2023-07-04 13:00:34 +00:00
wxiaoguang
9111d2d037
Manual backport of locale for 1.20 (2nd) (#25668)
The backport needs manually checking because some 1.21 strings might not
be right for 1.20

This backport also includes most fixes from
https://github.com/go-gitea/gitea/pull/25291#issuecomment-1617678658
(thanks to lunny's manual fix on Crowdin side)

Close #25638
2023-07-04 11:40:26 +00:00
Giteabot
5510ed34f1
Fix the nil pointer when assigning issues to projects (#25665) (#25677)
Backport #25665 by @Zettat123

Fixes #25649
Caused by #25468

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-07-04 10:59:01 +00:00
silverwind
39fce5750d
Prevent SVG shrinking (#25652) (#25669)
Backport https://github.com/go-gitea/gitea/pull/25652

This will prevent the most common cases of SVG shrinking because lack of
space. I evaluated multiple options and this seems to be the one with
the least impact in size and processing cost, so I went with it.

Unfortunately, CSS can not dynamically convert `16` obtained from
`attr()` to `16px`, or else a generic solution for all sizes would have
been possible. But a solution is [in

sight](https://developer.mozilla.org/en-US/docs/Web/CSS/attr#type-or-unit)
with `attr(width px)` but no browser supports it currently.
2023-07-04 10:03:03 +00:00
Giteabot
1f90376041
Fix show more for image on diff page (#25672) (#25673)
Backport #25672 by @HesterG

Right now when clicking on loadmore on files change page, if the loaded
content is image, it will be always in load status:


39e449b6-067a-474c-9443-9dd98d5bbfe2

This PR fixes this by adding `initImageDiff ` to `onShowMoreFiles `

After:


87bbb13e-0064-4a6e-a7ad-0f0060eb8bff

Co-authored-by: HesterG <hestergong@gmail.com>
2023-07-04 04:44:45 -04:00
Giteabot
0af6542a34
Add unit test for repository collaboration (#25640) (#25658)
Backport #25640 by @earl-warren

- Add a few extra test cases and test functions for the collaboration
model to get everything covered by tests (except for error handling, as
we cannot suddenly mock errors from the database).

```
-> % go tool cover -func=coverage.out | grep "code.gitea.io/gitea/models/repo/collaboration.go"
```

Before:
```
code.gitea.io/gitea/models/repo/collaboration.go:28:                            init                                            100.0%
code.gitea.io/gitea/models/repo/collaboration.go:39:                            GetCollaborators                                61.5%
code.gitea.io/gitea/models/repo/collaboration.go:65:                            CountCollaborators                              0.0%
code.gitea.io/gitea/models/repo/collaboration.go:70:                            GetCollaboration                                0.0%
code.gitea.io/gitea/models/repo/collaboration.go:83:                            IsCollaborator                                  100.0%
code.gitea.io/gitea/models/repo/collaboration.go:87:                            getCollaborations                               42.9%
code.gitea.io/gitea/models/repo/collaboration.go:102:                           ChangeCollaborationAccessMode                   77.8%
code.gitea.io/gitea/models/repo/collaboration.go:141:                           IsOwnerMemberCollaborator                       0.0%
```

After:
```
code.gitea.io/gitea/models/repo/collaboration.go:28:                            init                                            100.0%
code.gitea.io/gitea/models/repo/collaboration.go:39:                            GetCollaborators                                61.5%
code.gitea.io/gitea/models/repo/collaboration.go:65:                            CountCollaborators                              100.0%
code.gitea.io/gitea/models/repo/collaboration.go:70:                            GetCollaboration                                100.0%
code.gitea.io/gitea/models/repo/collaboration.go:83:                            IsCollaborator                                  100.0%
code.gitea.io/gitea/models/repo/collaboration.go:87:                            getCollaborations                               100.0%
code.gitea.io/gitea/models/repo/collaboration.go:102:                           ChangeCollaborationAccessMode                   83.3%
code.gitea.io/gitea/models/repo/collaboration.go:141:                           IsOwnerMemberCollaborator                       87.5%
```

Co-authored-by: Gusted <postmaster@gusted.xyz>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/825

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: silverwind <me@silverwind.io>
2023-07-04 06:29:43 +00:00
Giteabot
69bdcf41f3
Log the real reason when authentication fails (but don't show the user) (#25414) (#25660)
Backport #25414 by @lunny

Fix #24498

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-03 19:26:36 -04:00
Giteabot
e610b0389a
Fix UI misalignment on user setting page (#25629) (#25656)
Backport #25629 by @wxiaoguang

Fix #25628

Diff with ignoring space:
https://github.com/go-gitea/gitea/pull/25629/files?diff=unified&w=1

The "modal" shouldn't appear between "ui attached segment", otherwise
these segments lose margin-top.

After the fix:

<details>


![image](ac15e73d-4ca8-416a-950d-ffc912c6ab61)


![image](eb431336-4d21-4e44-8beb-8919595a83c8)


![image](dffaed88-5ba2-419d-a241-24cb200f757c)


![image](e8c5a03e-e16a-4c94-a1a5-7845d4e1a824)

</details>

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-07-03 21:16:58 +00:00
Lunny Xiao
13ffa287b1
Fix bug of branches API with tests(#25578) (#25579)
Backport #25578 

This PR added a repository's check when creating/deleting branches via
API. Mirror repository and archive repository cannot do that.
2023-07-03 16:17:30 +02:00
wxiaoguang
e5b684e567
Manuall backport of locale for 1.20 (#25635) 2023-07-03 16:14:25 +02:00
Giteabot
64ed262e18
Fix bug when change user name (#25637) (#25646)
Backport #25637 by @lunny

Fix #25621

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-07-03 14:23:35 +03:00
wxiaoguang
f51c8e0008
Make "cancel" buttons have proper type in modal forms (#25618) (#25641)
Replace #25446, fix #25438

All "cancel" buttons which do not have "type" should not submit the
form, should not be triggered by "Enter".

This is a complete fix for all modal dialogs.

The major change is "modules/aria/modal.js", "devtest" related code is
for demo/test purpose.
2023-07-03 17:09:38 +08:00
Giteabot
d8a59d5f12
use css on labels (#25626) (#25636)
Backport #25626 by @derelm

Changes html to use CSS label class similar to
`templates/shared/actions/runner_list.tmpl`

Before:

![grafik](6729d580-3ea6-4a90-972e-6e5117459da7)

After:

![grafik](d4fc280c-c40b-4db4-b1ba-877270f875c8)


List view (for reference - unchanged):

![grafik](5ad2d8d1-2fb5-414d-823b-48a368a74724)

Co-authored-by: derelm <465155+derelm@users.noreply.github.com>
2023-07-03 12:25:16 +08:00
Giteabot
1ddfe03131
Use AfterCommitId to get commit for Viewed functionality (#25529) (#25612)
Backport #25529 by @sebastian-sauer

the PullHeadCommitID is not always available when the PR is merged.

Not sure if this is the best solution but in my simple tests it looks
like this fixes the problem - happy to get any feedback.

hopefully fixes https://github.com/go-gitea/gitea/issues/24813

Co-authored-by: sebastian-sauer <sauer.sebastian@gmail.com>
2023-07-01 07:56:56 +08:00
Giteabot
24cf06592e
Restrict [actions].DEFAULT_ACTIONS_URL to only github or self (#25581) (#25604)
Backport #25581 by @wolfogre

Resolve #24789

## ⚠️ BREAKING ⚠️

Before this, `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like
`https://gitea.com` or `http://your-git-server,https://gitea.com`, and
the default value was `https://gitea.com`.

But now, `DEFAULT_ACTIONS_URL` supports only
`github`(`https://github.com`) or `self`(the root url of current Gitea
instance), and the default value is `github`.

If it has configured with a URL, an error log will be displayed and it
will fallback to `github`.

Actually, what we really want to do is always make it
`https://github.com`, however, this may not be acceptable for some
instances of internal use, so there's extra support for `self`, but no
more, even `https://gitea.com`.

Please note that `uses: https://xxx/yyy/zzz` always works and it does
exactly what it is supposed to do.

Although it's breaking, I belive it should be backported to `v1.20` due
to some security issues.

Follow-up on the runner side:

- https://gitea.com/gitea/act_runner/pulls/262
- https://gitea.com/gitea/act/pulls/70

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-30 07:53:00 +00:00
Giteabot
0b6f7fb607
read-only checkboxes don't appear and don't entirely act the way one might expect (#25573) (#25602)
Backport #25573 by @saegl5

This pull request fades read-only checkboxes and checkmark, and it makes
the checkboxes act more read-only/disabled by not changing the
border-color when clicked.

Examples using light mode:
 
| Before | After |
| - | - |
| ![Kapture 2023-06-28 at 00 20
45](0899fd5c-18a9-4290-9ba9-d3cf71033cf8)
| ![Kapture 2023-06-28 at 00 23
12](0db9be14-e16c-42ed-8fb1-999928fd1d25)
|
| ![Kapture 2023-06-28 at 00 25
22](65c6c380-b928-4e6c-b403-3655d3565896)
| ![Kapture 2023-06-28 at 00 27
28](d8c2a019-e07c-43a1-a7fa-93c0d4e01900)
|
| | read-only checkboxes and checkmark are faded<br>and the checkboxes
act more read-only/disabled |

Fixes/Closes/Resolves #25076

Co-authored-by: Ed Silkworth <ed.silkworth@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-29 22:53:43 +00:00
KN4CK3R
c27a3af728
Redirect to package after version deletion (#25594) (#25599)
Related #25559

Current behaviour:
1. Deletion of a package version
2. Redirect to the owners package list

New behaviour:
1. Deletion of a package version
2.1. If there are more versions available, redirect to the package again
2.2. If there are no versions available, redirect to the owners package
list
2023-06-30 00:14:57 +02:00
Giteabot
12aca3ef20
Add documentation about supported workflow trigger events (#25582) (#25589)
Backport #25582 by @Zettat123

Right now Gitea doesn't support all [Events that trigger
workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows).
This PR lists the supported events to help users write workflow files.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-06-29 14:51:46 +02:00
Giteabot
2390a46d0f
Reduce table padding globally (#25568) (#25577)
Backport #25568 by @silverwind

Fomantic's tables have too much padding. Reduce it so we have more
information density in them. Especially the admin tables need this
because they are bursting already because of column count.

## Admin repolist before and after

<img width="909" alt="Screenshot 2023-06-28 at 20 27 55"
src="954c925c-8db5-47ce-ae51-a2168b857014">
<img width="897" alt="Screenshot 2023-06-28 at 20 36 03"
src="0bddc09a-9117-48b3-a17e-3d34c58d8d3d">

## Other tables

<img width="1230" alt="Screenshot 2023-06-28 at 20 36 22"
src="38f555b6-a7ce-416a-9f1f-706eaf18863b">
<img width="1236" alt="Screenshot 2023-06-28 at 20 26 37"
src="82b2878e-358c-4dc2-a6b4-c66e43cd2dfb">
<img width="1231" alt="Screenshot 2023-06-28 at 20 59 30"
src="c6a92e55-a3a3-4c80-9a0d-50aebb49886c">

Files table is unaffected because it has custom padding already.

Co-authored-by: silverwind <me@silverwind.io>
2023-06-29 09:24:02 +00:00
Giteabot
51b6a78791
Fix milestones deletion (#25583) (#25584)
Backport #25583 by @HesterG

Close #25557 
Fix regression from #25315

`data-id` is still needed for deleting milestone.

Co-authored-by: HesterG <hestergong@gmail.com>
2023-06-29 08:52:05 +00:00
wxiaoguang
e6f62eea70
Do not prepare oauth2 config if it is not enabled, do not write config in some sub-commands (#25567) (#25576)
Backport #25567

Ref:

* https://github.com/go-gitea/gitea/issues/25377#issuecomment-1609757289

And some sub-commands like "generate" / "docs", they do not need to use
the ini config
2023-06-29 06:30:40 +02:00
Giteabot
8981f6d0fc
Fix content holes in Actions task logs file (#25560) (#25566)
Backport #25560 by @wolfogre

Fix #25451.

Bugfixes:
- When stopping the zombie or endless tasks, set `LogInStorage` to true
after transferring the file to storage. It was missing, it could write
to a nonexistent file in DBFS because `LogInStorage` was false.
- Always update `ActionTask.Updated` when there's a new state reported
by the runner, even if there's no change. This is to avoid the task
being judged as a zombie task.

Enhancement:
- Support `Stat()` for DBFS file.
- `WriteLogs` refuses to write if it could result in content holes.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-28 23:39:23 +00:00
Giteabot
b2b5c80cb2
Use correct response code in push mirror creation response in v1_json.tmpl (#25476) (#25571)
Backport #25476 by @GeorgDangl

In the process of doing a bit of automation via the API, we've
discovered a _small_ issue in the Swagger definition. We tried to create
a push mirror for a repository, but our generated client raised an
exception due to an unexpected status code.

When looking at this function:

3c7f5ed7b5/routers/api/v1/repo/mirror.go (L236-L240)

We see it defines `201 - Created` as response:

3c7f5ed7b5/routers/api/v1/repo/mirror.go (L260-L262)

But it actually returns `200 - OK`:

3c7f5ed7b5/routers/api/v1/repo/mirror.go (L373)

So I've just updated the Swagger definitions to match the code😀

Co-authored-by: Georg Dangl <10274404+GeorgDangl@users.noreply.github.com>
2023-06-28 19:00:56 -04:00
Giteabot
77db40e084
Fix bugs related to notification endpoints (#25548) (#25562)
Backport #25548 by @Zettat123

This PR
- fixes #25545
- fixes two incorrect `reqToken()` in `/notifications` endpoints (caused
by #24767)

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-06-28 16:24:48 +00:00
Giteabot
7222bac4e3
Align language menu icon and fit the footer area (#25556) (#25563)
Backport #25556 by @wxiaoguang

Close #25551

## Before


![image](f35aebeb-9c86-4632-8c1f-1c90aa757640)

## After



![image](4a623687-6c6d-442a-a4f9-07dadeb9fc6d)


----


![image](6a4e5ecf-b88b-4c22-98c3-21898bd41bc5)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-28 11:51:24 -04:00
wxiaoguang
0eb4ab4246
Fix sub-command log level (#25537) (#25553)
Backport #25537

More fix for #24981

* #24981

Close #22361, #25552

* #22361
* #25552

There were many patches for Gitea's sub-commands to satisfy the facts:

* Some sub-commands shouldn't output any log, otherwise the git protocol
would be broken
* Sometimes the users want to see "verbose" or "quiet" outputs

That's a longstanding problem, and very fragile. This PR is only a quick
patch for the problem.

In the future, the sub-command system should be refactored to a clear
solution.

----

Other changes:

* Use `ReplaceAllWriters` to replace
`RemoveAllWriters().AddWriters(writer)`, then it's an atomic operation.
* Remove unnecessary `syncLevelInternal` calls, because
`AddWriters/addWritersInternal` already calls it.
2023-06-28 17:35:20 +08:00
Giteabot
102dcfa3a0
Change Regenerate Secret button display (#25534) (#25541)
Backport #25534 by @KN4CK3R

Fixes #25527

Preview:

![grafik](a84ad1ba-43e6-42e5-a0e2-585fb226875d)

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-06-27 20:14:58 +00:00
Giteabot
614d6df2d8
Fix admin-dl-horizontal (#25512) (#25535)
Backport #25512 by @wxiaoguang


![image](fb731e07-da30-4470-8200-73b5ca8b78f1)


![image](85930b6f-5df7-437f-863f-423f3b81dd26)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: HesterG <hestergong@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-27 17:06:36 +00:00
Giteabot
345a25d016
Fix rerun icon on action view component (#25531) (#25536)
Backport #25531 by @HesterG

Right now rerun icon on action view component will not be seen when
duration text length is long, because the wrapper `job-brief-info` has a
fixed width, and the svg is squeezed. The way to fix this in this PR is
to change width to `fit-content` and exchange position of duration text
and rerun svg.

Before (rerun svg not shown on hover):

<img width="1401" alt="Screen Shot 2023-06-27 at 12 53 41"
src="bb3f62ec-8c56-4dbc-96f1-718b50426d91">

After:

<img width="1409" alt="Screen Shot 2023-06-27 at 12 50 59"
src="620aa02c-2326-408d-a763-453f48f42c40">

Co-authored-by: HesterG <hestergong@gmail.com>
2023-06-27 12:28:14 -04:00
Giteabot
e8a7cd4a1d
Fix input line-height cutting off g (#25334) (#25533)
Backport #25334 by @hiifong

Fix the incomplete display of input text
Before:

![image](6bd8ca29-a096-46a8-bd23-fb833f45186f)

![image](27e51e62-7150-45cd-8606-09317d462d70)
After:

![image](8d0db5d3-d768-42b4-9a75-0b8816f0a299)

![image](4193adc9-b635-4ed6-8e11-715ec5150563)

Co-authored-by: hiifong <i@hiif.ong>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-27 11:31:18 +02:00
6543
6c43b9f6f6
Changelog for v1.20.0-rc2 (#25474)
as title

---------

Co-authored-by: techknowlogick <matti@mdranta.net>
2023-06-27 12:08:37 +08:00
HesterG
40744f8976
Allow change line of admin-dl-horizontal dt (#25508) (#25516)
As https://github.com/go-gitea/gitea/pull/25515#issuecomment-1606626886
says, still need this backport

Close #25389

After:

<img width="915" alt="Screen Shot 2023-06-26 at 11 00 12"
src="45026447-cf50-4603-ade3-7b80a9023c20">


admin/dashboard:

<img width="957" alt="Screen Shot 2023-06-26 at 10 59 51"
src="f4f95bbe-f747-46f1-8fbd-5778a19ebef7">

Co-authored-by: Giteabot <teabot@gitea.io>
2023-06-26 22:20:22 +02:00
Giteabot
9d69a4758e
Add Adopt repository event and handler (#25497) (#25518)
Backport #25497 by @lunny

Fix #14304

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-06-26 20:09:07 +00:00
Giteabot
53747a58a0
Clarify the reason why the user can't add a new email if there is a pending activation (#25509) (#25514)
Backport #25509 by @wxiaoguang


![image](cff20df0-ad0c-4140-b8e2-5782cad8a53a)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-26 17:28:48 +08:00
Giteabot
00ba826360
Fix CLI sub-command handling (#25501) (#25517)
Backport #25501 by @wxiaoguang

A regression of #25330 : The nil "Action" should be treated as "help"

In old releases: `./gitea admin` show helps

After #25330: `./gitea admin` panics (although the code returned `nil`
if action is nil, but Golang's quirk is: nil in interface is not nil)

With this PR: `./gitea admin` shows helps as the old releases.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-26 16:41:52 +08:00
Giteabot
9bbb4d8d6d
Improve loadprojects for issue list (#25468) (#25493) 2023-06-26 02:06:58 +00:00
lonix1
5703a0d3e3
Document creating an API key from the CLI (#25504)
Related to https://github.com/go-gitea/gitea/issues/25503

---------

Co-authored-by: delvh <dev.lh@web.de>
2023-06-25 21:33:34 -04:00
Giteabot
85bad22ff8
Fine tune "dropdown button" icon (#25442) (#25499)
Backport #25442 by @wxiaoguang

![image](143e043d-85c9-43a4-85ae-ca55f507f738)

----

![image](bcba03a5-732e-4139-bc35-96a7f8bfcb88)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-25 14:35:26 +08:00
Giteabot
71d2a6a41a
Use InitWorkPathAndCfgProvider for environment-to-ini to avoid unnecessary checks (#25480) (#25488)
Backport #25480 by @wxiaoguang

Fix #25481

The `InitWorkPathAndCommonConfig` calls `LoadCommonSettings` which does
many checks like "current user is root or not".

Some commands like "environment-to-ini" shouldn't do such check, because
it might be run with "root" user at the moment (eg: the docker's setup
script)

ps: in the future, the docker's setup script should be improved to avoid
Gitea's command running with "root"

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-24 14:04:50 +00:00
wxiaoguang
d1f1f1142e
Revert "Make buttons in a modal form have proper type. (#25446) (#25478)" (#25484)
There is a side effect because some modal doesn't have a proper "ok"
button.

This reverts commit 050c38ca19.
2023-06-24 13:28:55 +00:00
Giteabot
2cd9d6b3f9
Fix wrong warn messages in migration steps (#25475) (#25487) 2023-06-24 17:51:26 +08:00
Denys Konovalov
050c38ca19
Make buttons in a modal form have proper type. (#25446) (#25478)
Backport #25446 by @wxiaoguang 

Fix  #25438

All non-"ok" buttons which do not have "type" should not submit the
form, should not be triggered by "Enter".

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-24 11:56:06 +08:00
Giteabot
51789ba12d
Improve wiki sidebar and TOC (#25460) (#25477)
Backport #25460 by @wxiaoguang

Close #20976
Close #20975

1. Fix the bug: the TOC in footer was incorrectly rendered as main
content's TOC
2. Fix the layout: on mobile, the TOC is put above the main content,
while the sidebar is put below the main content
3. Auto collapse the TOC on mobile

ps: many styles of "wiki.css" are moved from old css files, so leave
nits to following PRs.

### for desktop


![image](6c84201c-0648-465a-99e6-c53cdaee53c0)

### for mobile


![image](9cb4fdfe-b6ab-4e6f-ae82-219ddb8fa27e)

### other changed pages

<details>


![image](ef077736-2c3e-4e3d-82fe-d9bf1ebcca98)


![image](bb528429-ad5f-4258-a5c4-05f997c624ea)

</details>

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-24 10:16:15 +08:00
Giteabot
b0de3d08b8
Fix repo search broken because of profile page added (#25455) (#25467)
Backport #25455 by @lunny

Fix #25433 
Caused by #23260

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-06-23 15:48:35 +02:00
Giteabot
2e64449de7
Make "dismiss" content shown correctly (#25461) (#25465)
Backport #25461 by @wxiaoguang

Close #25127


![image](7d6be811-8e4a-4982-a5e4-857d171758d4)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-23 11:04:15 +00:00
John Olheiser
ec539b7a77
Gitea version in Makefile (#25456) (#25457)
Backport-ish of #25456

Signed-off-by: jolheiser <john.olheiser@gmail.com>
2023-06-23 09:52:03 +02:00
techknowlogick
6fbdacb524
Support Drone building binaries too 2023-06-22 21:13:54 -04:00
Denys Konovalov
948f6ca029
Remove test string (#25447) (#25448)
Backport #25447 

Remove test string on delete project button, I overlooked it in a
previous PR
2023-06-22 17:30:51 +00:00
wxiaoguang
061b68e995
Refactor path & config system (#25330) (#25416)
Backport #25330

# The problem

There were many "path tricks":

* By default, Gitea uses its program directory as its work path
* Gitea tries to use the "work path" to guess its "custom path" and
"custom conf (app.ini)"
* Users might want to use other directories as work path
* The non-default work path should be passed to Gitea by GITEA_WORK_DIR
or "--work-path"
* But some Gitea processes are started without these values
    * The "serv" process started by OpenSSH server
    * The CLI sub-commands started by site admin
* The paths are guessed by SetCustomPathAndConf again and again
* The default values of "work path / custom path / custom conf" can be
changed when compiling

# The solution

* Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use
test code to cover its behaviors.
* When Gitea's web server runs, write the WORK_PATH to "app.ini", this
value must be the most correct one, because if this value is not right,
users would find that the web UI doesn't work and then they should be
able to fix it.
* Then all other sub-commands can use the WORK_PATH in app.ini to
initialize their paths.
* By the way, when Gitea starts for git protocol, it shouldn't output
any log, otherwise the git protocol gets broken and client blocks
forever.

The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path
> env var GITEA_WORK_DIR > builtin default

The "app.ini" searching order is: cmd arg --config > cmd arg "work path
/ custom path" > env var "work path / custom path" > builtin default


## ⚠️ BREAKING

If your instance's "work path / custom path / custom conf" doesn't meet
the requirements (eg: work path must be absolute), Gitea will report a
fatal error and exit. You need to set these values according to the
error log.
2023-06-22 16:27:18 +00:00
John Olheiser
734fd93f59
Move some regexp out of functions (#25430) (#25445)
Partial backport of #25430

Not a bug, but worth backporting for efficiency.

Signed-off-by: jolheiser <john.olheiser@gmail.com>
2023-06-22 16:01:54 +00:00
Giteabot
203fe2841d
Fix Permission in API returned repository struct (#25388) (#25441)
Backport #25388 by @wolfogre

The old code generates `structs.Repository.Permissions` with only
`access.Permission.AccessMode`, however, it should check the units too,
or the value could be incorrect. For example,
`structs.Repository.Permissions.Push` could be false even the doer has
write access to code unit.

Should fix
https://github.com/renovatebot/renovate/issues/14059#issuecomment-1047961128
(Not reported by it, I just found it when I was looking into this bug)

---

Review tips:

The major changes are
- `modules/structs/repo.go`
https://github.com/go-gitea/gitea/pull/25388/files#diff-870406f6857117f8b03611c43fca0ab9ed6d6e76a2d0069a7c1f17e8fa9092f7
- `services/convert/repository.go`
https://github.com/go-gitea/gitea/pull/25388/files#diff-7736f6d2ae894c9edb7729a80ab89aa183b888a26a811a0c1fdebd18726a7101

And other changes are passive.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-22 17:01:42 +02:00
Giteabot
056829749e
Diff page enhancements (#25398) (#25437)
Backport #25398 by @silverwind

Two small tweaks:

1. Vertically center arrow here when editing a PR:

<img width="405" alt="Screenshot 2023-06-20 at 19 48 49"
src="1d63764d-9fd9-467e-8a8e-9258c06475eb">

2. Use 2-row layout on diff viewed status and show it again on mobile:

<img width="142" alt="Screenshot 2023-06-20 at 19 51 21"
src="3046e782-163c-4f87-910c-a22066de8f1b">

Mobile view:

<img width="370" alt="Screenshot 2023-06-20 at 19 44 40"
src="9cf56347-7323-4d05-99a5-17ad215ee44d">

Co-authored-by: silverwind <me@silverwind.io>
2023-06-22 14:33:13 +02:00
Giteabot
f18b8e7d8a
Change default email domain for LDAP users (#25425) (#25434)
Backport #25425 by @Zettat123

Fixes #21169

Change `localhost` to `localhost.local`

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-06-22 10:53:15 +00:00
Giteabot
ea00ed320d
Various UI fixes (#25264) (#25431)
Backport #25264 by @silverwind

Numerous small UI fixes:

- Fix double border in collaborator list
- Fix system notice table background
- Mute links in repo and org lists
- Downsize projects edit buttons
- Improve milestones and project list rendering
- Condense milestone list entry to a single line of "metas"
- Mute ".." button in repo files list

<img width="899" alt="Screenshot 2023-06-14 at 21 19 23"
src="40d70006-5f76-49ad-b43c-4343ec3311e1">

<img width="905" alt="Screenshot 2023-06-14 at 21 18 29"
src="46ef39ea-ab26-452d-89b0-a55d0cfacfdb">

<img width="270" alt="Screenshot 2023-06-14 at 21 14 09"
src="aa16e833-a03b-4231-bc7c-159a6a6bee19">

<img width="409" alt="Screenshot 2023-06-14 at 21 12 13"
src="b5242d41-f87a-4837-b0cf-9cc4c1f43daf">

<img width="286" alt="Screenshot 2023-06-14 at 21 10 03"
src="d0c36e47-651b-4d34-ad95-3d59474a7c3e">

<img width="928" alt="Screenshot 2023-06-14 at 21 05 24"
src="fc3b713e-d252-40f5-b6ba-6e5a741ab500">

<img width="217" alt="Screenshot 2023-06-14 at 21 02 01"
src="c4c33376-18d6-4820-aff5-f508f6d351a0">
<img width="79" alt="Screenshot 2023-06-14 at 20 42 43"
src="034b5950-c0bf-473b-a2f7-0c27a0259f29">
<img width="607" alt="Screenshot 2023-06-14 at 21 00 42"
src="fba2d3fd-bd3e-4daf-8b2f-530a1c99c8bc">

Co-authored-by: silverwind <me@silverwind.io>
2023-06-22 10:19:38 +00:00
sebastian-sauer
30a783879f
Show outdated comments in files changed tab (#24936) (#25428)
Backport #24936

If enabled show a clickable label in the comment. A click on the label
opens the Conversation tab with the comment focussed - there you're able
to view the old diff (or original diff the comment was created on).

**Screenshots**


![image](63ab9571-a9ee-4900-9f02-94ab0095f9e7)


![image](78f7c225-8d76-46f5-acfd-9b8aab988a6c)

When resolved and outdated:


![image](6ece9ebd-c792-4aa5-9c35-628694e9d093)

Option to enable/disable this (stored in user settings - default is
disabled):


![image](ed99dfe4-76dc-4c12-bd96-e7e62da50ab5)


![image](e837a052-e92e-4a28-906d-9db5bacf93a6)

fixes #24913

Co-authored-by: silverwind <me@silverwind.io>
2023-06-22 08:34:42 +00:00
wxiaoguang
cb3173a1e9
Use "utf8mb4" for MySQL by default (#25432)
TBH, I don't see much difference from `Remove "CHARSET" config option
for MySQL, always use "utf8mb4"` #25413

Close #25413
2023-06-22 07:38:23 +02:00
Giteabot
ffe089432f
Fix missing commit message body when the message has leading newlines (#25418) (#25422)
Backport #25418 by @wolfogre

Commit with `echo "\nmessage after a blank line\nsecond line of the
message" | git commit --cleanup=verbatim -F -` and push.

<img width="1139" alt="image"
src="f9a2c28c-e307-4c78-9e31-3d3ace7b9274">

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-21 13:55:08 +02:00
Giteabot
8302b95d6b
Avoid polluting config file when "save" (#25395) (#25406)
Backport #25395 by @wxiaoguang

That's a longstanding INI package problem: the "MustXxx" calls change
the option values, and the following "Save" will save a lot of garbage
options into the user's config file.

Ideally we should refactor the INI package to a clear solution, but it's
a huge work.

A clear workaround is what this PR does: when "Save", load a clear INI
instance and save it.

Partially fix #25377, the "install" page needs more fine tunes.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-21 04:51:26 +00:00
Giteabot
6f1c95ec5b
Use the new download domain replace the old (#25405) (#25409)
Backport #25405 by @lunny

As title.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-06-21 03:59:51 +00:00
Giteabot
cda69a0363
Fix dropdown icon layout on diff page (#25397) (#25403)
Backport #25397 by @wxiaoguang

Address
https://github.com/go-gitea/gitea/pull/25163#issuecomment-1599207916

Remove the unused  "icon-button".

And fix the layout:

Without the dropdown icon:

```
	{{svg "gitea-whitespace"}}
```


![image](58a524ba-f289-4982-aea2-6f9f9f9cbdcf)


With the dropdown icon:

```
	{{svg "gitea-whitespace" 16 "gt-mr-3"}}
	{{svg "octicon-triangle-down" 14 "dropdown icon"}}
```


![image](eb99168b-5d49-40a7-8665-5296cbb4e486)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-21 10:55:11 +08:00
Giteabot
4908cc9adf
Fix blank dir message when uploading files from web editor (#25391) (#25400)
Backport #25391 by @lunny

Fix #7883

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-06-20 20:28:52 +00:00
Giteabot
28ed763f55
Use Actions git context instead of dynamically created buildkit one (#25381) (#25383)
Backport #25381 by @techknowlogick

The [docker/build-push-action@v2
action](https://github.com/docker/build-push-action) by default ignores
the checkout created using the actions/checkout@v2 action. When you pass
a git build context to docker build, it wouldn't include the .git
directory.

By passing `context: .` to the build step then it'll use the Actions git
context which includes the git fetch from the earlier step.

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-06-20 08:52:38 +02:00
silverwind
8e89eb8f43
Update JS dependencies, remove space after emoji completion (#25266) (#25352)
Manual backport of #25266 because of lockfile conflicts.

- Update all JS dependencies
- Enable stylint
[`media-feature-name-value-no-unknown`](https://stylelint.io/user-guide/rules/media-feature-name-value-no-unknown)
- Make use of new features in webpack and text-expander-element
- Tested Swagger and Mermaid

To explain the `text-expander-element` change: Before this version, the
element added a unavoidable space after emoji completion. Now that
https://github.com/github/text-expander-element/pull/36 is in, we gain
control over this space and I opted to remove it for emoji completion
and retain it for `@` mentions.

Co-authored-by: Giteabot <teabot@gitea.io>
2023-06-20 05:38:52 +00:00
Giteabot
dfefe86045
Fix LDAP sync when Username Attribute is empty (#25278) (#25379)
Backport #25278 by @Zettat123

Fix #21072


![image](96b30beb-7f88-4a60-baae-2e5ad8049555)

Username Attribute is not a required item when creating an
authentication source. If Username Attribute is empty, the username
value of LDAP user cannot be read, so all users from LDAP will be marked
as inactive by mistake when synchronizing external users.

This PR improves the sync logic, if username is empty, the email address
will be used to find user.

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-06-20 13:11:22 +08:00
Giteabot
10fcb55507
Fetch all git data for embedding correct version in docker image (#25361) (#25373)
Backport #25361 by @techknowlogick

Fix #25350

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-06-20 03:07:51 +00:00
Giteabot
e9105ac281
Fix label list divider (#25312) (#25372)
Backport #25312 by @wxiaoguang

We only needs 2 lines to hide the dividers.

```
  $dropdownLabelFilter.dropdown('setting', {'hideDividers': 'empty'});
  $dropdownLabelFilter.dropdown('refreshItems');
```

Other code blocks are refactored by the way.


![image](74989996-fcea-4df4-b534-b06f7957939a)


![image](ee3b3761-b96e-4fb5-b646-e9d3117e5f40)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-19 18:14:31 +00:00
Giteabot
e6e1cfd8e4
fix issue filters on mobile view (#25368) (#25371)
Backport #25368 by @denyskon

Fix #24846 applying the solution proposed by @silverwind 

<details>
<summary>Screenshots</summary>


![Bildschirmfoto vom 2023-06-19
12-20-54](3f4e4536-38c4-451b-bfc0-a7c39acd37f0)
![Bildschirmfoto vom 2023-06-19
12-21-02](3403ecc2-4d7f-4acd-b0c0-1b7a10228ff7)
![Bildschirmfoto vom 2023-06-19
12-21-16](ef28a2bf-b7cc-4aec-b54b-99d2cc46a1f6)
![Bildschirmfoto vom 2023-06-19
12-21-21](f8cd72a5-379e-410b-b0ef-d58895719370)
![Bildschirmfoto vom 2023-06-19
12-21-28](34c78301-820c-4106-a086-ae81dc97eb91)
![Bildschirmfoto vom 2023-06-19
12-21-48](b677adf1-3a48-42c8-befe-fa9d2679f0a3)
</details>



Replaces #25335

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-06-19 17:43:22 +00:00
Giteabot
072997692c
Fix incorrect actions ref_name (#25358) (#25367)
Backport #25358 by @nephatrine

Fix #25357 .

Just a simple fix the result of `${{ gitea.ref_name }}` to show the
shortened name rather than the full ref.

Co-authored-by: Daniel Wolf <1461334+nephatrine@users.noreply.github.com>
2023-06-19 18:37:52 +02:00
Giteabot
e9fab3ea3e
Avoid polluting the config (#25345) (#25354)
Backport #25345 by @wxiaoguang

Caught by #25330

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-18 20:56:50 +00:00
wxiaoguang
e0bd6ebabd
Fix incorrect config argument position for builtin SSH server (#25341)
The "--config" option is a global option, it shouldn't appear at the
end.

Otherwise it might not be respected in some cases.

Caught by #25330 and use a separate PR to fix it for 1.20
2023-06-18 16:56:21 +00:00
Giteabot
cc73f6e821
Add Exoscale to installation on cloud provider docs (#25342) (#25346)
Backport #25342 by @pmig

We created a Gitea application for the [Exoscale
Marketplace](https://www.exoscale.com/marketplace/listing/glasskube-gitea/)
for easier installation on the European cloud provider.

The installation is managed via the [Glasskube Kubernetes
Operator](https://github.com/glasskube/operator).

Signed-off-by: Philip Miglinci <pmig@glasskube.eu>
Co-authored-by: Philip Miglinci <p.miglinci@gmail.com>
2023-06-18 15:48:06 +00:00
Giteabot
ff18c3ba65
Write absolute AppDataPath to app.ini when installing (#25331) (#25347)
Backport #25331 by @wxiaoguang

If the APP_DATA_PATH isn't written into the config when installing, then
its value is uncertain because some Gitea command doesn't run with
correct WorkPath.

This is a quick fix for #25330  and can be backported.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-18 15:07:46 +00:00
Giteabot
b673edbeaf
Fix UI on mobile view (#25315) (#25340)
Backport #25315 by @denyskon

Various fixes to pages or elements which were looking ugly on mobile.
<details>
<summary>Screenshots</summary>

![Bildschirmfoto vom 2023-06-17
20-38-41](30b5d3ce-df3b-43eb-a4c2-c3790667fb9d)
![Bildschirmfoto vom 2023-06-17
20-39-27](27c07b25-3602-4fb2-b34d-d5e875e054e9)

![Bildschirmfoto vom 2023-06-17
20-41-27](dacdbb4e-e3dd-4b94-abf0-c68e3d64bd3b)
![Bildschirmfoto vom 2023-06-17
20-41-48](72432c35-7c4a-4c7f-a767-3562f26a5c14)
![Bildschirmfoto vom 2023-06-17
20-42-37](737c26ed-1910-4467-98ef-e8769bbbe6f0)
![Bildschirmfoto vom 2023-06-17
20-42-52](1813b4bc-43c0-4912-8acb-5d799c090bf3)
![Bildschirmfoto vom 2023-06-17
20-43-06](136466e8-34e5-419d-97ec-5202ff819fd2)
![Bildschirmfoto vom 2023-06-17
20-43-42](59270bb2-d661-4a84-8504-3e50f771f767)
![Bildschirmfoto vom 2023-06-17
20-44-44](494e274d-3771-4141-9419-0a4bbd8b7f64)

</details>

Co-authored by: @silverwind

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-18 13:02:41 +00:00
derelm
05431593ef
Fix action runner last online state on edit page (#25337)
Backport fix for action runner last online state not showing in
`release/v1.20` - fixes #25336
2023-06-18 12:04:35 +00:00
Giteabot
aa4c9c3215
build nightly docker images (#25317) (#25333)
Backport #25317 by @techknowlogick

followup of https://github.com/go-gitea/gitea/pull/25308 this time to
build & push nightly docker images

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-06-18 11:09:30 +00:00
Giteabot
4e79c76ed0
When viewing a file, hide the add button (#25320) (#25339)
Backport #25320 by @hiifong

Fix #25281
When viewing a file, hide the add button

![image](839babaf-6b67-46e1-a103-677306fb8503)

![image](32a8aeca-31f4-4ce1-a0fa-7656e16b66d3)

Co-authored-by: hiifong <i@hiif.ong>
2023-06-18 10:24:54 +00:00
Giteabot
3bd311c3f4
Remove EasyMDE focus outline on text (#25328) (#25332)
Backport #25328 by @silverwind

EasyMDE in Firefox currently shows a ugly outline in the fake textarea
the CodeMirror uses. Hide it.

Before:

<img width="845" alt="Screenshot 2023-06-18 at 02 54 09"
src="dc406166-9ad5-4a9b-9581-002b5cdcc6df">

After:

<img width="870" alt="Screenshot 2023-06-18 at 02 54 24"
src="ddd78759-2cf2-4385-b863-7576fec25c34">

Co-authored-by: silverwind <me@silverwind.io>
2023-06-18 09:35:40 +02:00
techknowlogick
7e06e6a042
use Actions environment variables in Makefile (#25319) (#25318) 2023-06-17 23:27:04 -04:00
Giteabot
e5629d9701
Remove more unused Fomantic variants (#25292) (#25323)
Backport #25292 by @silverwind

Save another 50KB of CSS by removing unused and useless Fomantic
variants.

Removed the last instance of a `tertiary` button and fixed a TODO:

<img width="509" alt="Screenshot 2023-06-15 at 22 34 36"
src="8a16ae7b-2b17-439b-a096-60a52724e3d6">

Co-authored-by: silverwind <me@silverwind.io>
2023-06-17 17:14:25 +02:00
Giteabot
4ea38bba73
Build nightly binaries with Actions (#25308) (#25314)
Backport #25308 by @techknowlogick

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-16 19:59:20 +02:00
Giteabot
25cb1fb994
Fix displayed RPM repo url (#25310) (#25313)
Backport #25310 by @KN4CK3R

Fixes #25302

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-06-16 16:39:41 +00:00
Giteabot
e5422db5c7
Show if File is Executable (#25287) (#25300)
Backport #25287 by @JakobDev

This simply shows if a File has the executable Permission


![grafik](1d50c105-6d55-4ecc-808a-c9cd5559d238)

Co-authored-by: JakobDev <jakobdev@gmx.de>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-16 09:29:26 +00:00
Giteabot
3a29f6aaff
Add link to support page for commercial support (#25293) (#25297)
Backport #25293 by @techknowlogick

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-06-16 14:26:32 +08:00
Giteabot
99d71b2b65
Docs about how to generate config for act runner with docker and setup it with docker-compose (#25256) (#25296)
Backport #25256 by @thezzisu

In this pull request, the following changes are addressed:

- State user should create `config.yaml` before start container to avoid
errors.
- Provided instructions to deploy runners using docker compose.

Co-authored-by: Zisu Zhang <thezzisu@gmail.com>
2023-06-16 12:09:03 +08:00
Giteabot
783f7ccb2c
Fix some UI alignments (#25277) (#25290)
Backport #25277 by @wxiaoguang

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

Fix the problems:

1. The `repo-button-row` had various patches before, this PR makes it
consistent
2. The "Add File" has wrong CSS class "icon", remove it
3. The "Add File" padding was overridden by "!important", fix it by
`.repo-button-row .button.dropdown` with comment
4. The selector `.ui.segments ~ .ui.top.attached.header` is incorrect,
it should use `+`

The `repo-button-row` is only used on 3 pages:


![image](16057ff0-7d30-41ca-ac13-70d074364566)


![image](cbe2acb1-07b6-48f3-9f28-407a75f8c4ed)


![image](341416b3-f6a8-466f-a140-361ee80e53a7)


![image](3d4b7857-ef99-4a3f-a667-9890714a096d)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-16 00:32:59 +00:00
Giteabot
3f75fbf8fe
Remove fomantic inverted variations (#25286) (#25289)
Backport #25286 by @silverwind

Remove all Fomantic `inverted` variations, we are no using any of them.
This reduces the index CSS bundle by 98kB.

Co-authored-by: silverwind <me@silverwind.io>
2023-06-15 12:13:03 -04:00
Giteabot
4124f8ef70
Fix issue and commit status popup padding (#25254) (#25288)
Backport #25254 by @wxiaoguang

Close #25249

Use "dialog" for the role



![image](2b5b7552-48bc-4ecf-947b-34917232cff9)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-15 16:52:23 +02:00
Giteabot
b45ea0280b
Show OAuth2 errors to end users (#25261) (#25271)
Backport #25261 by @wxiaoguang

Partially fix #23936


![image](8aa7f3ad-a5f0-42ce-a478-289a03bd08a3)


![image](bb901e7d-485a-47a5-b68d-9ebe7013a6b2)


![image](9a1ce0f3-f011-4baf-8e2f-cc6304bc9703)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-15 02:48:36 +00:00
Giteabot
031ddfcb7b
Fix index generation parallelly failure (#25235) (#25269)
Backport #25235 by @lunny

Fix #22109

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-15 02:14:00 +00:00
yp05327
d686aa0d31
Fix profile render when the README.md size is larger than 1024 bytes (#25270)
Backport #25131
2023-06-15 01:39:34 +00:00
Giteabot
037366f93f
Fix edit OAuth application width (#25262) (#25263)
Backport #25262 by @denyskon

The `<div class="ui container">` broke the width of this section - fix
by removing it.

Before:

![grafik](df655636-0dc3-4c8a-9778-a14c80fc807b)

After:

![grafik](e74c74be-097e-43fb-a698-92337aa128e9)

Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-06-14 20:39:51 -04:00
Giteabot
5191ab6445
Use flex to align SVG and text (#25163) (#25260)
Backport #25163 by @wxiaoguang

The code can be as simple as:

```html
<div class="flex-text-block">{{svg "octicon-alert"}} {{svg "octicon-x"}} text (block)</div>
<div><div class="flex-text-inline">{{svg "octicon-alert"}} {{svg "octicon-x"}} text</div> (inline)</div>
<div><button class="ui red button">{{svg "octicon-alert" 24}} {{svg "octicon-x" 24}} text</button></div>
```


![image](1d3c10f1-0bc7-4c26-b236-bad537d5c465)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-14 13:21:48 -04:00
Giteabot
bfd3eb9dbc
GitHub Actions enhancements for frontend (#25150) (#25259)
Backport #25150 by @silverwind

- Don't run DB tests for frontend-only changes
- Build frontend as part of frontend step
- Build everything when actions change

Co-authored-by: silverwind <me@silverwind.io>
2023-06-14 15:59:46 +00:00
Giteabot
8fa9d9dcc9
Fix panic when migrating a repo from GitHub with issues (#25246) (#25247)
Backport #25246 by @wolfogre

Fix #25245. Regression of #23946.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-14 07:08:08 +00:00
Giteabot
21cd5c2f3d
Fix all possible setting error related storages and added some tests (#23911) (#25244)
Backport #23911 by @lunny

Follow up #22405

Fix #20703 

This PR rewrites storage configuration read sequences with some breaks
and tests. It becomes more strict than before and also fixed some
inherit problems.

- Move storage's MinioConfig struct into setting, so after the
configuration loading, the values will be stored into the struct but not
still on some section.
- All storages configurations should be stored on one section,
configuration items cannot be overrided by multiple sections. The
prioioty of configuration is `[attachment]` > `[storage.attachments]` |
`[storage.customized]` > `[storage]` > `default`
- For extra override configuration items, currently are `SERVE_DIRECT`,
`MINIO_BASE_PATH`, `MINIO_BUCKET`, which could be configured in another
section. The prioioty of the override configuration is `[attachment]` >
`[storage.attachments]` > `default`.
- Add more tests for storages configurations.
- Update the storage documentations.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-14 08:36:52 +02:00
Giteabot
22948048b2
Revert overflow: overlay (revert #21850) (#25231) (#25239)
Backport #25231 by @wxiaoguang

It causes not only one issue like #25221 (the footer width was also
affected by that change and was fixed some time ago)

The problem of "overflow: overlay" (#21850) is:

* It's not widely supported and is non-standard
https://caniuse.com/css-overflow-overlay
* It's not widely tested in Gitea (some standard layout like `ui
container + ui grid` may break it).
* The benefit seems smaller than the problems it brings.

So, I think it is good to revert it.

----

Let's leave enough time for testing and reviewing.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-13 19:45:45 +00:00
Giteabot
fa28d0e706
Do not overwrite the log mode when installing (#25203) (#25209)
Backport #25203 by @wxiaoguang

Fix #24861

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-13 20:35:37 +02:00
Giteabot
3ea544d89c
Change access token UI to select dropdowns (#25109) (#25230)
Backport #25109 by @jtran

The current UI to create API access tokens uses checkboxes that have a
complicated relationship where some need to be checked and/or disabled
in certain states. It also requires that a user interact with it to
understand what their options really are.

This branch changes to use `<select>`s. It better fits the available
options, and it's closer to [GitHub's
UI](https://github.com/settings/personal-access-tokens/new), which is
good, in my opinion. It's more mobile friendly since the tap-areas are
larger. If we ever add more permissions, like Maintainer, there's a
natural place that doesn't take up more screen real-estate.

This branch also fixes a few minor issues:

- Hide the error about selecting at least one permission after second
submission
- Fix help description to call it "authorization" since that's what
permissions are about (not authentication)

Related: #24767.

<img width="883" alt="Screenshot 2023-06-07 at 5 07 34 PM"
src="6b63d807-c9be-4a4b-8e53-ecab6cbb8f76">

---

When it's open:

<img width="881" alt="Screenshot 2023-06-07 at 5 07 59 PM"
src="2432c6d0-39c2-4ca4-820e-c878ffdbfb69">

Co-authored-by: Jonathan Tran <jon@allspice.io>
2023-06-13 12:42:25 +00:00
Giteabot
9cef7a4600
Use inline SVG for built-in OAuth providers (#25171) (#25234)
Backport #25171 by @silverwind

The plan is that all built-in auth providers use inline SVG for more
flexibility in styling and to get the GitHub icon to follow
`currentcolor`. This only removes the `public/img/auth` directory and
adds the missing svgs to our svg build.

It should map the built-in providers to these SVGs and render them. If
the user has set a Icon URL, it should render that as an `img` tag
instead.

```
gitea-azure-ad
gitea-bitbucket
gitea-discord
gitea-dropbox
gitea-facebook
gitea-gitea
gitea-gitlab
gitea-google
gitea-mastodon
gitea-microsoftonline
gitea-nextcloud
gitea-twitter
gitea-yandex
octicon-mark-github
```

GitHub logo is now white again on dark theme:

<img width="431" alt="Screenshot 2023-06-12 at 21 45 34"
src="27a43504-d60a-4132-a502-336b25883e4d">

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-13 12:04:40 +00:00
Giteabot
c207b94e0c
Fix task list checkbox toggle to work with YAML front matter (#25184) (#25227)
Backport #25184 by @jtran

Fixes #25160.

`data-source-position` of checkboxes in a task list was incorrect
whenever there was YAML front matter. This would result in issue content
or PR descriptions getting corrupted with random `x` or space characters
when a user checked or unchecked a task.

Co-authored-by: Jonathan Tran <jon@allspice.io>
2023-06-13 08:23:21 +00:00
Giteabot
506c70884a
Fix compatible for webhook ref type (#25195) (#25223)
Backport #25195 by @lunny

Fix #25185 
Caused by #24634

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-06-13 06:51:50 +00:00
Giteabot
f64f5495af
Hide limited users if viewed by anonymous ghost (#25214) (#25220)
Backport #25214 by @KN4CK3R

The ghost user leads to inclusion of limited users/orgs in
`BuildCanSeeUserCondition`.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-06-13 13:53:26 +08:00
Giteabot
3e9fc36729
Remove hacky patch for "safari emoji glitch fix" (#25208) (#25211)
Backport #25208 by @wxiaoguang

According to my test, the UI (emoji) is fine in Safari

And actually the code is just dead code, because the "resize" event is
never fired on page loading. So for most cases users just view the pages
without this hacky patch, nobody ever complains.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-12 18:16:04 +02:00
Giteabot
8e798ebbdf
Fix fullscreen for action (#25200) (#25207)
Backport #25200 by @HesterG

An error occurs when clicking on `show full screen` on action page.

<img width="1440" alt="Screen Shot 2023-06-12 at 13 06 52"
src="1d4ded3c-fb77-4dd8-9201-24d0696f96eb">


class name has changed in #25134, so the selector is not working. 
Enhance the selectors to fix this.

Co-authored-by: HesterG <hestergong@gmail.com>
2023-06-12 15:41:13 +00:00
Giteabot
0ad5ae0dbf
Improve some documents: release version, logging, NFS lock (#25202) (#25204)
Backport #25202 by @wxiaoguang

Close #23654

Close #24684


@techknowlogick I still think we need to rename
https://dl.gitea.com/gitea/1.20/ to
https://dl.gitea.com/gitea/1.20-nightly/

`/gitea/1.20/` is quite confusing, it needs these words to explain why.
If we call it `1.20-nightly`, the FAQ can be simplified a lot.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-12 23:17:43 +08:00
Giteabot
0cf467e9e0
Minor arc-green color tweaks (#25175) (#25205)
Backport #25175 by @silverwind

Some minor color tweaks

<img width="1271" alt="Screenshot 2023-06-09 at 13 29 25"
src="b7b34995-5d34-461f-8d19-4f5755a98109">
<img width="1272" alt="Screenshot 2023-06-09 at 13 31 20"
src="63c866b4-797e-46ed-ba28-b1162ccd3e15">
<img width="1276" alt="Screenshot 2023-06-09 at 13 32 21"
src="de7ee02e-d0c7-4979-a8aa-0fd03e8db491">

Co-authored-by: silverwind <me@silverwind.io>
2023-06-12 13:56:59 +02:00
Giteabot
5ff0f7d0ca
Add WithPullRequest for actionsNotifier (#25144) (#25197)
Backport #25144 by @Zettat123

Fix #25093

If
[`WithPullRequest`](679b1f7949/services/actions/notifier_helper.go (L90-L96))
is not called, the `Ref` in
[`notifyInput`](679b1f7949/services/actions/notifier_helper.go (L55-L65))
will be empty, so the workflows in the head branch will not be found and
triggered.
2023-06-12 17:08:09 +08:00
silverwind
224ee0d4e5
Fix strange UI behavior of cancelling dismiss review modal (#25172)
Backport clean cherry-picks of
https://github.com/go-gitea/gitea/pull/25133 and
https://github.com/go-gitea/gitea/pull/25162 to 1.20.
2023-06-11 02:54:30 +00:00
silverwind
ee26d1c578
Button and color enhancements (#24989) (#25176)
Backport #24989. Clean cherry-pick aside from one small conflict with
divider.

- Various corrections to button styles, especially secondary
- Remove focus highlight, it's annoying when it stays on button after
press
- Clearly define ghost and link buttons with demos in devtest
- Remove black, grey and tertiary buttons, they should not be used
- Make `arc-green` slightly darker

<img width="1226" alt="image"

src="8d89786a-01ab-40f8-ae5a-e17f40e35084">
<img width="1249" alt="image"

src="83651e6d-3c27-46ff-b8bd-ff344d70e949">
2023-06-11 02:13:08 +00:00
Giteabot
18093d4c9a
Fix mobile navbar and misc cleanups (#25134) (#25169)
Backport #25134 by @silverwind

- Fix and improve mobile navbar layout
- Apply all cleanups suggested in
https://github.com/go-gitea/gitea/pull/25111
- Make media query breakpoints match Fomantic's exactly
- Clean up whitespace in class on navbar items

Mobile navbar before and after:
<img width="745" alt="Screenshot 2023-06-08 at 08 40 56"
src="ca84b239-b10f-41db-8c06-dcf2b6dd9d28">
<img width="739" alt="Screenshot 2023-06-08 at 08 41 23"
src="09133c54-eb7e-4110-858c-ead23c3b7521">
2023-06-11 09:50:39 +08:00
Giteabot
de1d14590d
Fix bug for code search if code is disabled (#25173) (#25181)
Backport #25173 by @lunny

Fix https://github.com/go-gitea/gitea/pull/24189/files#r1224144768

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-06-09 19:32:55 +00:00
Giteabot
0058453fd9
Modify OAuth login ui and fix display name, iconurl related logic (#25030) (#25161)
Backport #25030 by @HesterG

Close #24808 

Co-Authour @wxiaoguang @silverwind 

1. Most svgs are found from https://worldvectorlogo.com/ , and some are
from conversion of png to svg. (facebook and nextcloud). And also
changed `templates/user/settings/security/accountlinks.tmpl`.

2. Fixed display name and iconurl related logic

# After

<img width="1436" alt="Screen Shot 2023-06-05 at 14 09 05"
src="a5db39d8-1ab0-4676-82a4-fba60a1d1f84">

On mobile

<img width="378" alt="Screen Shot 2023-06-05 at 14 09 46"
src="71d0f51b-baac-4f48-8ca2-ae0e013bd62e">


user/settings/security/accountlinks (The dropdown might be improved
later)

<img width="973" alt="Screen Shot 2023-06-01 at 10 01 44"
src="27010e7e-2785-4fc5-8c49-b06621898f37">

Co-authored-by: HesterG <hestergong@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-09 10:59:18 +00:00
Giteabot
7679f4d51a
Fix open redirect check for more cases (#25143) (#25154)
Backport #25143 by @lafriks

If redirect_to parameter has set value starting with `\\example.com`
redirect will be created with header `Location: /\\example.com` that
will redirect to example.com domain.

Co-authored-by: Lauris BH <lauris@nix.lv>
2023-06-08 18:03:42 +02:00
Giteabot
82a8c26bbf
Update js dependencies (#25137) (#25151)
Backport #25137 by @silverwind

- Update all JS dependencies
- Tweak eslint for more generic globs
- Tested mermaid, pdf, monaco

Co-authored-by: silverwind <me@silverwind.io>
2023-06-08 12:05:13 +00:00
Giteabot
cb113991a3
Remove incorrect element ID on "post-install" page (#25104) (#25129)
Backport #25104 by @wxiaoguang

That ID is a "copy&paste" error, it conflicts with the
`initRepoMigrationStatusChecker` logic, which is the right function for
a real `#repo_migrating` element. That wrong ID causes incorrect page
navigation after installation.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-08 11:22:32 +00:00
Giteabot
0bf07a7f61
Improve notification icon and navbar (#25111) (#25124)
Backport #25111 by @silverwind

Improvements to the notification icon and `<nav>`:

- Add a opaque color for header hover and use it, allowing the border to
be the right color on hover (sadly, not otherwise possible with CSS, not
even `color-mix`).
- Increase font size by 1px
- Use flexbox for slightly better text centering
- Reduce padding of user and add repo button, add margin on right side
of user menu
- Remove the `following bar` wrapper on navbar

<img width="176" alt="Screenshot 2023-06-07 at 00 07 08"
src="23cdc3d6-7f63-49df-bec3-f2e75e32a304">
<img width="63" alt="Screenshot 2023-06-07 at 00 07 14"
src="fae602c2-4467-4d50-b1ec-56317843f9a2">
<img width="84" alt="Screenshot 2023-06-07 at 00 07 36"
src="c48141b8-0b3c-48cc-846a-3a272524dbdb">
<img width="329" alt="Screenshot 2023-06-07 at 00 25 10"
src="cda612f1-426e-466b-a351-fc992bfd18fd">
<img width="186" alt="Screenshot 2023-06-07 at 00 35 45"
src="04484a2e-9bbf-493c-aa26-8e936da008fa">
<img width="797" alt="Screenshot 2023-06-07 at 16 57 40"
src="e7ccb672-5807-4cb6-b306-b18ae0c7e321">

---------

Co-authored-by: silverwind <me@silverwind.io>
2023-06-08 10:19:13 +00:00
Giteabot
adb5b9c061
Fix incorrect git ignore rule and add missing license files (#25135) (#25138)
Backport #25135 by @yp05327

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-06-08 09:41:08 +00:00
HesterG
f0c967560a
Change branch name from master to main in some documents' links (#25126) (#25140)
Backport #25126 to 1.20

As title. And needs to backport to 1.19
2023-06-08 09:40:05 +00:00
Giteabot
1cc63ade82
Fix MilestoneIDs when querying issues (#25125) (#25141)
Backport #25125 by @Zettat123

Fix #25114

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-06-08 11:03:53 +02:00
5689 changed files with 252590 additions and 281475 deletions

View file

@ -2,25 +2,12 @@ root = "."
tmp_dir = ".air" tmp_dir = ".air"
[build] [build]
pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs
cmd = "make --no-print-directory backend" cmd = "make --no-print-directory backend"
bin = "gitea" bin = "gitea"
delay = 2000 delay = 1000
include_ext = ["go", "tmpl"] include_ext = ["go", "tmpl"]
include_file = ["main.go"] include_file = ["main.go"]
include_dir = ["cmd", "models", "modules", "options", "routers", "services"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"]
exclude_dir = [ exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"]
"models/fixtures",
"models/migrations/fixtures",
"modules/avatar/identicon/testdata",
"modules/avatar/testdata",
"modules/git/tests",
"modules/migration/file_format_testdata",
"routers/private/tests",
"services/gitdiff/testdata",
]
exclude_regex = ["_test.go$", "_gen.go$"] exclude_regex = ["_test.go$", "_gen.go$"]
stop_on_error = true stop_on_error = true
[log]
main_only = true

View file

@ -13,47 +13,46 @@ groups:
- -
name: BREAKING name: BREAKING
labels: labels:
- pr/breaking - kind/breaking
- -
name: SECURITY name: SECURITY
labels: labels:
- topic/security - kind/security
- -
name: FEATURES name: FEATURES
labels: labels:
- type/feature - kind/feature
-
name: ENHANCEMENTS
labels:
- type/enhancement
-
name: PERFORMANCE
labels:
- performance/memory
- performance/speed
- performance/bigrepo
- performance/cpu
-
name: BUGFIXES
labels:
- type/bug
- -
name: API name: API
labels: labels:
- modifies/api - kind/api
-
name: ENHANCEMENTS
labels:
- kind/enhancement
- kind/refactor
- kind/ui
-
name: BUGFIXES
labels:
- kind/bug
- -
name: TESTING name: TESTING
labels: labels:
- type/testing - kind/testing
-
name: TRANSLATION
labels:
- kind/translation
- -
name: BUILD name: BUILD
labels: labels:
- topic/build - kind/build
- topic/code-linting - kind/lint
- -
name: DOCS name: DOCS
labels: labels:
- type/docs - kind/docs
- -
name: MISC name: MISC
default: true default: true

View file

@ -1,42 +0,0 @@
{
"name": "Gitea DevContainer",
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
"features": {
// installs nodejs into container
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"
},
"ghcr.io/devcontainers/features/git-lfs:1.2.2": {},
"ghcr.io/devcontainers-extra/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
},
"customizations": {
"vscode": {
"settings": {},
// same extensions as Gitpod, should match /.gitpod.yml
"extensions": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"golang.go",
"stylelint.vscode-stylelint",
"DavidAnson.vscode-markdownlint",
"Vue.volar",
"ms-azuretools.vscode-docker",
"vitest.explorer",
"cweijan.vscode-database-client2",
"GitHub.vscode-pull-request-github",
"Azurite.azurite"
]
}
},
"portsAttributes": {
"3000": {
"label": "Gitea Web",
"onAutoForward": "notify"
}
},
"postCreateCommand": "make deps"
}

View file

@ -14,7 +14,7 @@ _test
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin* __debug_bin
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]
@ -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
@ -53,6 +62,7 @@ cpu.out
/data /data
/indexers /indexers
/log /log
/public/img/avatar
/tests/integration/gitea-integration-* /tests/integration/gitea-integration-*
/tests/integration/indexers-* /tests/integration/indexers-*
/tests/e2e/gitea-e2e-* /tests/e2e/gitea-e2e-*
@ -65,18 +75,27 @@ cpu.out
/yarn.lock /yarn.lock
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/public/assets/js /public/js
/public/assets/css /public/css
/public/assets/fonts /public/fonts
/public/assets/img/avatar /public/img/webpack
/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
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft # Snapcraft
snap/.snapcraft/ snap/.snapcraft/
parts/ parts/

426
.drone.yml Normal file
View file

@ -0,0 +1,426 @@
---
kind: pipeline
name: release-version
platform:
os: linux
arch: amd64
workspace:
base: /source
path: /
trigger:
event:
- tag
volumes:
- name: deps
temp: {}
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: deps-frontend
image: node:20
pull: always
commands:
- make deps-frontend
- name: deps-backend
image: gitea/test_env:linux-1.20-amd64
pull: always
commands:
- make deps-backend
volumes:
- name: deps
path: /go
- name: static
image: techknowlogick/xgo:go-1.20.x
pull: always
commands:
# Upgrade to node 20 once https://github.com/techknowlogick/xgo/issues/163 is resolved
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get -qqy install nodejs
- export PATH=$PATH:$GOPATH/bin
- make release
environment:
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
TAGS: bindata sqlite sqlite_unlock_notify
DEBIAN_FRONTEND: noninteractive
depends_on: [fetch-tags]
volumes:
- name: deps
path: /go
- name: gpg-sign
image: plugins/gpgsign:1
pull: always
settings:
detach_sign: true
excludes:
- "dist/release/*.sha256"
files:
- "dist/release/*"
environment:
GPGSIGN_KEY:
from_secret: gpgsign_key
GPGSIGN_PASSPHRASE:
from_secret: gpgsign_passphrase
depends_on: [static]
- name: release-tag
image: woodpeckerci/plugin-s3:latest
pull: always
settings:
acl:
from_secret: aws_s3_acl
region:
from_secret: aws_s3_region
bucket:
from_secret: aws_s3_bucket
endpoint:
from_secret: aws_s3_endpoint
path_style:
from_secret: aws_s3_path_style
source: "dist/release/*"
strip_prefix: dist/release/
target: "/gitea/${DRONE_TAG##v}"
environment:
AWS_ACCESS_KEY_ID:
from_secret: aws_access_key_id
AWS_SECRET_ACCESS_KEY:
from_secret: aws_secret_access_key
depends_on: [gpg-sign]
- name: github
image: plugins/github-release:latest
pull: always
settings:
files:
- "dist/release/*"
file_exists: overwrite
environment:
GITHUB_TOKEN:
from_secret: github_token
depends_on: [gpg-sign]
---
kind: pipeline
type: docker
name: docker-linux-amd64-release-version
platform:
os: linux
arch: amd64
trigger:
ref:
include:
- "refs/tags/**"
exclude:
- "refs/tags/**-rc*"
paths:
exclude:
- "docs/**"
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
image: plugins/docker:latest
pull: always
settings:
auto_tag: true
auto_tag_suffix: linux-amd64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: plugins/docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: true
auto_tag_suffix: linux-amd64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: docker-linux-amd64-release-candidate-version
platform:
os: linux
arch: amd64
trigger:
ref:
- "refs/tags/**-rc*"
paths:
exclude:
- "docs/**"
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
image: plugins/docker:latest
pull: always
settings:
tags: ${DRONE_TAG##v}-linux-amd64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: plugins/docker:latest
settings:
dockerfile: Dockerfile.rootless
tags: ${DRONE_TAG##v}-linux-amd64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: docker-linux-arm64-release-version
platform:
os: linux
arch: arm64
trigger:
ref:
include:
- "refs/tags/**"
exclude:
- "refs/tags/**-rc*"
paths:
exclude:
- "docs/**"
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
image: plugins/docker:latest
pull: always
settings:
auto_tag: true
auto_tag_suffix: linux-arm64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: plugins/docker:latest
settings:
dockerfile: Dockerfile.rootless
auto_tag: true
auto_tag_suffix: linux-arm64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: docker-linux-arm64-release-candidate-version
platform:
os: linux
arch: arm64
trigger:
ref:
- "refs/tags/**-rc*"
paths:
exclude:
- "docs/**"
steps:
- name: fetch-tags
image: docker:git
pull: always
commands:
- git fetch --tags --force
- name: publish
image: plugins/docker:latest
pull: always
settings:
tags: ${DRONE_TAG##v}-linux-arm64
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
- name: publish-rootless
image: plugins/docker:latest
settings:
dockerfile: Dockerfile.rootless
tags: ${DRONE_TAG##v}-linux-arm64-rootless
repo: gitea/gitea
build_args:
- GOPROXY=https://goproxy.io
password:
from_secret: docker_password
username:
from_secret: docker_username
environment:
PLUGIN_MIRROR:
from_secret: plugin_mirror
DOCKER_BUILDKIT: 1
when:
event:
exclude:
- pull_request
---
kind: pipeline
type: docker
name: docker-manifest-version
platform:
os: linux
arch: amd64
steps:
- name: manifest-rootless
image: plugins/manifest
pull: always
settings:
auto_tag: true
ignore_missing: true
spec: docker/manifest.rootless.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
- name: manifest
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
spec: docker/manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
trigger:
ref:
- "refs/tags/**"
paths:
exclude:
- "docs/**"
depends_on:
- docker-linux-amd64-release-version
- docker-linux-amd64-release-candidate-version
- docker-linux-arm64-release-version
- docker-linux-arm64-release-candidate-version

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

1
.envrc
View file

@ -1 +0,0 @@
use flake

File diff suppressed because it is too large Load diff

752
.eslintrc.yaml Normal file
View file

@ -0,0 +1,752 @@
root: true
reportUnusedDisableDirectives: true
ignorePatterns:
- /web_src/js/vendor
parserOptions:
sourceType: module
ecmaVersion: latest
plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
- eslint-plugin-array-func
- eslint-plugin-custom-elements
- eslint-plugin-import
- eslint-plugin-jquery
- eslint-plugin-no-jquery
- eslint-plugin-no-use-extend-native
- eslint-plugin-regexp
- eslint-plugin-sonarjs
- eslint-plugin-unicorn
- eslint-plugin-wc
env:
es2022: true
node: true
globals:
__webpack_public_path__: true
overrides:
- 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: ["build/generate-images.js"]
rules:
import/no-unresolved: [0]
import/no-extraneous-dependencies: [0]
- files: ["*.config.*"]
rules:
import/no-unused-modules: [0]
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]
accessor-pairs: [2]
array-bracket-newline: [0]
array-bracket-spacing: [2, never]
array-callback-return: [2, {checkForEach: true}]
array-element-newline: [0]
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]
arrow-parens: [2, always]
arrow-spacing: [2, {before: true, after: true}]
block-scoped-var: [2]
brace-style: [2, 1tbs, {allowSingleLine: true}]
camelcase: [0]
capitalized-comments: [0]
class-methods-use-this: [0]
comma-dangle: [2, only-multiline]
comma-spacing: [2, {before: false, after: true}]
comma-style: [2, last]
complexity: [0]
computed-property-spacing: [2, never]
consistent-return: [0]
consistent-this: [0]
constructor-super: [2]
curly: [0]
custom-elements/expose-class-on-global: [0]
custom-elements/extends-correct-class: [2]
custom-elements/file-name-matches-element: [2]
custom-elements/no-constructor: [2]
custom-elements/no-customized-built-in-elements: [2]
custom-elements/no-dom-traversal-in-attributechangedcallback: [2]
custom-elements/no-dom-traversal-in-connectedcallback: [2]
custom-elements/no-exports-with-element: [2]
custom-elements/no-method-prefixed-with-on: [2]
custom-elements/no-unchecked-define: [0]
custom-elements/one-element-per-file: [0]
custom-elements/tag-name-matches-class: [2]
custom-elements/valid-tag-name: [2]
default-case-last: [2]
default-case: [0]
default-param-last: [0]
dot-location: [2, property]
dot-notation: [0]
eol-last: [2]
eqeqeq: [2]
for-direction: [2]
func-call-spacing: [2, never]
func-name-matching: [2]
func-names: [0]
func-style: [0]
function-call-argument-newline: [0]
function-paren-newline: [0]
generator-star-spacing: [0]
getter-return: [2]
grouped-accessor-pairs: [2]
guard-for-in: [0]
id-blacklist: [0]
id-length: [0]
id-match: [0]
implicit-arrow-linebreak: [0]
import/consistent-type-specifier-style: [0]
import/default: [0]
import/dynamic-import-chunkname: [0]
import/export: [2]
import/exports-last: [0]
import/extensions: [2, always, {ignorePackages: true}]
import/first: [2]
import/group-exports: [0]
import/max-dependencies: [0]
import/named: [2]
import/namespace: [0]
import/newline-after-import: [0]
import/no-absolute-path: [0]
import/no-amd: [2]
import/no-anonymous-default-export: [0]
import/no-commonjs: [2]
import/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}]
import/no-default-export: [0]
import/no-deprecated: [0]
import/no-dynamic-require: [0]
import/no-empty-named-blocks: [2]
import/no-extraneous-dependencies: [2]
import/no-import-module-exports: [0]
import/no-internal-modules: [0]
import/no-mutable-exports: [0]
import/no-named-as-default-member: [0]
import/no-named-as-default: [2]
import/no-named-default: [0]
import/no-named-export: [0]
import/no-namespace: [0]
import/no-nodejs-modules: [0]
import/no-relative-packages: [0]
import/no-relative-parent-imports: [0]
import/no-restricted-paths: [0]
import/no-self-import: [2]
import/no-unassigned-import: [0]
import/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
import/no-unused-modules: [2, {unusedExports: true}]
import/no-useless-path-segments: [2, {commonjs: true}]
import/no-webpack-loader-syntax: [2]
import/order: [0]
import/prefer-default-export: [0]
import/unambiguous: [0]
indent: [2, 2, {SwitchCase: 1}]
init-declarations: [0]
jquery/no-ajax-events: [2]
jquery/no-ajax: [0]
jquery/no-animate: [2]
jquery/no-attr: [0]
jquery/no-bind: [2]
jquery/no-class: [0]
jquery/no-clone: [2]
jquery/no-closest: [0]
jquery/no-css: [0]
jquery/no-data: [0]
jquery/no-deferred: [2]
jquery/no-delegate: [2]
jquery/no-each: [0]
jquery/no-extend: [2]
jquery/no-fade: [0]
jquery/no-filter: [0]
jquery/no-find: [0]
jquery/no-global-eval: [2]
jquery/no-grep: [2]
jquery/no-has: [2]
jquery/no-hide: [2]
jquery/no-html: [0]
jquery/no-in-array: [2]
jquery/no-is-array: [2]
jquery/no-is-function: [2]
jquery/no-is: [0]
jquery/no-load: [2]
jquery/no-map: [0]
jquery/no-merge: [2]
jquery/no-param: [2]
jquery/no-parent: [0]
jquery/no-parents: [0]
jquery/no-parse-html: [2]
jquery/no-prop: [0]
jquery/no-proxy: [2]
jquery/no-ready: [2]
jquery/no-serialize: [2]
jquery/no-show: [2]
jquery/no-size: [2]
jquery/no-sizzle: [0]
jquery/no-slide: [0]
jquery/no-submit: [0]
jquery/no-text: [0]
jquery/no-toggle: [2]
jquery/no-trigger: [0]
jquery/no-trim: [2]
jquery/no-val: [0]
jquery/no-when: [2]
jquery/no-wrap: [2]
key-spacing: [2]
keyword-spacing: [2]
line-comment-position: [0]
linebreak-style: [2, unix]
lines-around-comment: [0]
lines-between-class-members: [0]
logical-assignment-operators: [0]
max-classes-per-file: [0]
max-depth: [0]
max-len: [0]
max-lines-per-function: [0]
max-lines: [0]
max-nested-callbacks: [0]
max-params: [0]
max-statements-per-line: [0]
max-statements: [0]
multiline-comment-style: [2, separate-lines]
multiline-ternary: [0]
new-cap: [0]
new-parens: [2]
newline-per-chained-call: [0]
no-alert: [0]
no-array-constructor: [2]
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-confusing-arrow: [0]
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: [2]
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-extra-parens: [0]
no-extra-semi: [2]
no-fallthrough: [2]
no-floating-decimal: [0]
no-func-assign: [2]
no-global-assign: [2]
no-implicit-coercion: [2]
no-implicit-globals: [0]
no-implied-eval: [2]
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: [0]
no-jquery/no-and-self: [2]
no-jquery/no-animate-toggle: [2]
no-jquery/no-animate: [2]
no-jquery/no-append-html: [0]
no-jquery/no-attr: [0]
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: [0]
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: [0]
no-jquery/no-data: [0]
no-jquery/no-deferred: [2]
no-jquery/no-delegate: [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-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: [0]
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: [0]
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: [0]
no-jquery/no-parse-html-literal: [0]
no-jquery/no-parse-html: [2]
no-jquery/no-parse-json: [2]
no-jquery/no-parse-xml: [2]
no-jquery/no-prop: [0]
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: [0]
no-jquery/no-slide: [2]
no-jquery/no-sub: [2]
no-jquery/no-support: [2]
no-jquery/no-text: [0]
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: [0]
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-mixed-operators: [0]
no-mixed-spaces-and-tabs: [2]
no-multi-assign: [0]
no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true}}]
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: [2]
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]
no-return-assign: [0]
no-return-await: [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-tabs: [2]
no-template-curly-in-string: [2]
no-ternary: [0]
no-this-before-super: [2]
no-throw-literal: [2]
no-trailing-spaces: [2]
no-undef-init: [2]
no-undef: [2, {typeof: true}]
no-undefined: [0]
no-underscore-dangle: [0]
no-unexpected-multiline: [2]
no-unmodified-loop-condition: [2]
no-unneeded-ternary: [0]
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: [2, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_, ignoreRestSiblings: false}]
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-whitespace-before-property: [2]
no-with: [0] # handled by no-restricted-syntax
nonblock-statement-body-position: [2]
object-curly-newline: [0]
object-curly-spacing: [2, never]
object-shorthand: [2, always]
one-var-declaration-per-line: [0]
one-var: [0]
operator-assignment: [2, always]
operator-linebreak: [2, after]
padded-blocks: [2, never]
padding-line-between-statements: [0]
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: [0]
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]
quote-props: [0]
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}]
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-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-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-star-quantifier: [2]
regexp/prefer-unicode-codepoint-escapes: [2]
regexp/prefer-w: [0]
regexp/require-unicode-regexp: [0]
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]
require-unicode-regexp: [0]
require-yield: [2]
rest-spread-spacing: [2, never]
semi-spacing: [2, {before: false, after: true}]
semi-style: [2, last]
semi: [2, always, {omitLastInOneLineBlock: true}]
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]
space-before-blocks: [2, always]
space-in-parens: [2, never]
space-infix-ops: [2]
space-unary-ops: [2]
spaced-comment: [2, always]
strict: [0]
switch-colon-spacing: [2]
symbol-description: [2]
template-curly-spacing: [2, never]
template-tag-spacing: [2, never]
unicode-bom: [2, never]
unicorn/better-regex: [0]
unicorn/catch-error-name: [0]
unicorn/consistent-destructuring: [2]
unicorn/consistent-function-scoping: [2]
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-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-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-remove-event-listener: [2]
unicorn/no-keyword-prefix: [0]
unicorn/no-lonely-if: [2]
unicorn/no-negated-condition: [0]
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-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-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
unicorn/no-unsafe-regex: [0]
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: [2, {ignoreUsedVariables: true}]
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-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: [0]
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-replace-all: [0]
unicorn/prefer-string-slice: [0]
unicorn/prefer-string-starts-ends-with: [2]
unicorn/prefer-string-trim-start-end: [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/guard-super-call: [2]
wc/no-closed-shadow-root: [2]
wc/no-constructor-attributes: [2]
wc/no-constructor-params: [2]
wc/no-invalid-element-name: [0] # covered by custom-elements/valid-tag-name
wc/no-self-class: [2]
wc/no-typos: [2]
wc/require-listener-teardown: [2]
wrap-iife: [2, inside]
wrap-regex: [0]
yield-star-spacing: [2, after]
yoda: [2, never]

6
.gitattributes vendored
View file

@ -1,10 +1,10 @@
* text=auto eol=lf * text=auto eol=lf
*.tmpl linguist-language=Handlebars *.tmpl linguist-language=Handlebars
*.pb.go linguist-generated
/assets/*.json linguist-generated /assets/*.json linguist-generated
/public/assets/img/svg/*.svg linguist-generated /public/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

@ -3,9 +3,9 @@
<!-- <!--
1. Please speak English, this is the language all maintainers can speak and write. 1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord 2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://forum.gitea.com). server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your issue doesn't already exist. 3. Please take a moment to check that your issue doesn't already exist.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq) 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because 5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report. incomplete details will be handled as an invalid report.
--> -->
@ -21,12 +21,12 @@
- [ ] MySQL - [ ] MySQL
- [ ] MSSQL - [ ] MSSQL
- [ ] SQLite - [ ] SQLite
- Can you reproduce the bug at https://demo.gitea.com: - Can you reproduce the bug at https://try.gitea.io:
- [ ] Yes (provide example URL) - [ ] Yes (provide example URL)
- [ ] No - [ ] No
- Log gist: - Log gist:
<!-- It really is important to provide pertinent logs --> <!-- It really is important to provide pertinent logs -->
<!-- Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help --> <!-- Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems -->
<!-- In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini --> <!-- In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini -->
## Description ## Description

1
.github/FUNDING.yml vendored
View file

@ -1 +1,2 @@
open_collective: gitea open_collective: gitea
custom: https://www.bountysource.com/teams/gitea

View file

@ -1,91 +1,94 @@
name: Bug Report name: Bug Report
description: Found something you weren't expecting? Report it here! description: Found something you weren't expecting? Report it here!
labels: ["type/bug"] labels: kind/bug
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
1. Please speak English, this is the language all maintainers can speak and write. 1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord 2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://forum.gitea.com). server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Make sure you are using the latest release and 3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before. take a moment to check that your issue hasn't been reported before.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq) 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. It's really important to provide pertinent details and logs (https://docs.gitea.com/help/support), 5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report. incomplete details will be handled as an invalid report.
- type: textarea 6. In particular it's really important to provide pertinent logs. You must give us DEBUG level logs.
id: description Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
attributes: In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
label: Description - type: textarea
description: | id: description
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) attributes:
If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services. label: Description
- type: input description: |
id: gitea-ver Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
attributes: If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services.
label: Gitea Version - type: input
description: Gitea version (or commit reference) of your instance id: gitea-ver
validations: attributes:
required: true label: Gitea Version
- type: dropdown description: Gitea version (or commit reference) of your instance
id: can-reproduce validations:
attributes: required: true
label: Can you reproduce the bug on the Gitea demo site? - type: dropdown
description: | id: can-reproduce
If so, please provide a URL in the Description field attributes:
URL of Gitea demo: https://demo.gitea.com label: Can you reproduce the bug on the Gitea demo site?
options: description: |
- "Yes" If so, please provide a URL in the Description field
- "No" URL of Gitea demo: https://try.gitea.io
validations: options:
required: true - "Yes"
- type: markdown - "No"
attributes: validations:
value: | required: true
It's really important to provide pertinent logs - type: markdown
Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help attributes:
In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini value: |
- type: input It's really important to provide pertinent logs
id: logs Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems
attributes: In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
label: Log Gist - type: input
description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden id: logs
- type: textarea attributes:
id: screenshots label: Log Gist
attributes: description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden
label: Screenshots - type: textarea
description: If this issue involves the Web Interface, please provide one or more screenshots id: screenshots
- type: input attributes:
id: git-ver label: Screenshots
attributes: description: If this issue involves the Web Interface, please provide one or more screenshots
label: Git Version - type: input
description: The version of git running on the server id: git-ver
- type: input attributes:
id: os-ver label: Git Version
attributes: description: The version of git running on the server
label: Operating System - type: input
description: The operating system you are using to run Gitea id: os-ver
- type: textarea attributes:
id: run-info label: Operating System
attributes: description: The operating system you are using to run Gitea
label: How are you running Gitea? - type: textarea
description: | id: run-info
Please include information on whether you built Gitea yourself, used one of our downloads, are using https://demo.gitea.com or are using some other package attributes:
Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc. label: How are you running Gitea?
If you are using a package or systemd tell us what distribution you are using description: |
validations: Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package
required: true Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc.
- type: dropdown If you are using a package or systemd tell us what distribution you are using
id: database validations:
attributes: required: true
label: Database - type: dropdown
description: What database system are you running? id: database
options: attributes:
- PostgreSQL label: Database
- MySQL/MariaDB description: What database system are you running?
- MSSQL options:
- SQLite - PostgreSQL
- MySQL
- MSSQL
- SQLite

View file

@ -7,11 +7,11 @@ contact_links:
url: https://discord.gg/Gitea url: https://discord.gg/Gitea
about: Please ask questions and discuss configuration or deployment problems here. about: Please ask questions and discuss configuration or deployment problems here.
- name: Discourse Forum - name: Discourse Forum
url: https://forum.gitea.com url: https://discourse.gitea.io
about: Questions and configuration or deployment problems can also be discussed on our forum. about: Questions and configuration or deployment problems can also be discussed on our forum.
- name: Frequently Asked Questions - name: Frequently Asked Questions
url: https://docs.gitea.com/help/faq url: https://docs.gitea.io/en-us/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.

View file

@ -1,24 +1,24 @@
name: Feature Request name: Feature Request
description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here!
labels: ["type/proposal"] labels: ["kind/feature", "kind/proposal"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
1. Please speak English, this is the language all maintainers can speak and write. 1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord 2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://forum.gitea.com). server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your feature hasn't already been suggested. 3. Please take a moment to check that your feature hasn't already been suggested.
- type: textarea - type: textarea
id: description id: description
attributes: attributes:
label: Feature Description label: Feature Description
placeholder: | placeholder: |
I think it would be great if Gitea had... I think it would be great if Gitea had...
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: screenshots id: screenshots
attributes: attributes:
label: Screenshots label: Screenshots
description: If you can, provide screenshots of an implementation on another site e.g. GitHub description: If you can, provide screenshots of an implementation on another site e.g. GitHub

View file

@ -1,66 +1,66 @@
name: Web Interface Bug Report name: Web Interface Bug Report
description: Something doesn't look quite as it should? Report it here! description: Something doesn't look quite as it should? Report it here!
labels: ["type/bug", "topic/ui"] labels: ["kind/bug", "kind/ui"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue.
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
1. Please speak English, this is the language all maintainers can speak and write. 1. Please speak English, this is the language all maintainers can speak and write.
2. Please ask questions or configuration/deploy problems on our Discord 2. Please ask questions or configuration/deploy problems on our Discord
server (https://discord.gg/gitea) or forum (https://forum.gitea.com). server (https://discord.gg/gitea) or forum (https://discourse.gitea.io).
3. Please take a moment to check that your issue doesn't already exist. 3. Please take a moment to check that your issue doesn't already exist.
4. Make sure it's not mentioned in the FAQ (https://docs.gitea.com/help/faq) 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq)
5. Please give all relevant information below for bug reports, because 5. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report. incomplete details will be handled as an invalid report.
6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript 6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript
error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us
DEBUG level logs. (See https://docs.gitea.com/administration/logging-config#collecting-logs-for-help) DEBUG level logs. (See https://docs.gitea.io/en-us/logging-configuration/#debugging-problems)
- type: textarea - type: textarea
id: description id: description
attributes: attributes:
label: Description label: Description
description: | description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services. If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services.
- type: textarea - type: textarea
id: screenshots id: screenshots
attributes: attributes:
label: Screenshots label: Screenshots
description: Please provide at least 1 screenshot showing the issue. description: Please provide at least 1 screenshot showing the issue.
validations: validations:
required: true required: true
- type: input - type: input
id: gitea-ver id: gitea-ver
attributes: attributes:
label: Gitea Version label: Gitea Version
description: Gitea version (or commit reference) your instance is running description: Gitea version (or commit reference) your instance is running
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: can-reproduce id: can-reproduce
attributes: attributes:
label: Can you reproduce the bug on the Gitea demo site? label: Can you reproduce the bug on the Gitea demo site?
description: | description: |
If so, please provide a URL in the Description field If so, please provide a URL in the Description field
URL of Gitea demo: https://demo.gitea.com URL of Gitea demo: https://try.gitea.io
options: options:
- "Yes" - "Yes"
- "No" - "No"
validations: validations:
required: true required: true
- type: input - type: input
id: os-ver id: os-ver
attributes: attributes:
label: Operating System label: Operating System
description: The operating system you are using to access Gitea description: The operating system you are using to access Gitea
- type: input - type: input
id: browser-ver id: browser-ver
attributes: attributes:
label: Browser Version label: Browser Version
description: The browser and version that you are using to access Gitea description: The browser and version that you are using to access Gitea
validations: validations:
required: true required: true

View file

@ -1,7 +0,0 @@
self-hosted-runner:
labels:
- actuated-4cpu-8gb
- actuated-4cpu-16gb
- nscloud
- namespace-profile-gitea-release-docker
- namespace-profile-gitea-release-binary

83
.github/labeler.yml vendored
View file

@ -1,83 +0,0 @@
modifies/docs:
- changed-files:
- any-glob-to-any-file:
- "**/*.md"
- "docs/**"
modifies/templates:
- changed-files:
- all-globs-to-any-file:
- "templates/**"
- "!templates/swagger/v1_json.tmpl"
modifies/api:
- changed-files:
- any-glob-to-any-file:
- "routers/api/**"
- "templates/swagger/v1_json.tmpl"
modifies/cli:
- changed-files:
- any-glob-to-any-file:
- "cmd/**"
modifies/translation:
- changed-files:
- any-glob-to-any-file:
- "options/locale/*.ini"
modifies/migrations:
- changed-files:
- any-glob-to-any-file:
- "models/migrations/**"
modifies/internal:
- changed-files:
- any-glob-to-any-file:
- ".air.toml"
- "Makefile"
- "Dockerfile"
- "Dockerfile.rootless"
- ".dockerignore"
- "docker/**"
- ".editorconfig"
- ".eslintrc.cjs"
- ".golangci.yml"
- ".gitpod.yml"
- ".markdownlint.yaml"
- ".spectral.yaml"
- "stylelint.config.js"
- ".yamllint.yaml"
- ".github/**"
- ".gitea/**"
- ".devcontainer/**"
- "build.go"
- "build/**"
- "contrib/**"
modifies/dependencies:
- changed-files:
- any-glob-to-any-file:
- "package.json"
- "package-lock.json"
- "pyproject.toml"
- "poetry.lock"
- "go.mod"
- "go.sum"
modifies/go:
- changed-files:
- any-glob-to-any-file:
- "**/*.go"
modifies/frontend:
- changed-files:
- any-glob-to-any-file:
- "*.js"
- "*.ts"
- "web_src/**"
docs-update-needed:
- changed-files:
- any-glob-to-any-file:
- "custom/conf/app.example.ini"

View file

@ -2,9 +2,8 @@
Please check the following: Please check the following:
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports. 1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md . 2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
3. For documentations contribution, please go to https://gitea.com/gitea/docs 3. Describe what your pull request does and which issue you're targeting (if any).
4. Describe what your pull request does and which issue you're targeting (if any). 4. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
5. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily. 5. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
6. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`. 6. Delete all these tips before posting.
7. Delete all these tips before posting.
<!-- end tips --> <!-- end tips -->

54
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,54 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 14
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- status/blocked
- kind/security
- lgtm/done
- reviewed/confirmed
- priority/critical
- kind/proposal
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had recent activity.
I am here to help clear issues left open even if solved or waiting for more insight.
This issue will be closed if no further activity occurs during the next 2 weeks.
If the issue is still valid just add a comment to keep it alive.
Thank you for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been automatically closed because of inactivity.
You can re-open it if needed.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 1
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
pulls:
daysUntilStale: 60
daysUntilClose: 60
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs during the next 2 months. Thank you
for your contributions.
closeComment: >
This pull request has been automatically closed because of inactivity.
You can re-open it if needed.

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:
@ -10,15 +10,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea' if: github.repository == 'go-gitea/gitea'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version-file: go.mod go-version: ">=1.20.1"
check-latest: true - run: make generate-license generate-gitignore
- run: make 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.2
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot

22
.github/workflows/cron-lock.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: cron-lock
on:
schedule:
- cron: "0 0 * * *" # every day at 00:00 UTC
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
action:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea'
steps:
- uses: dessant/lock-threads@v4
with:
issue-inactive-days: 45

View file

@ -10,24 +10,19 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea' if: github.repository == 'go-gitea/gitea'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: crowdin/github-action@v1 - name: download from crowdin
with: uses: docker://jonasfranz/crowdin
upload_sources: true
upload_translations: false
download_sources: false
download_translations: true
push_translations: false
push_sources: false
create_pull_request: false
config: crowdin.yml
env: env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }} CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
PLUGIN_DOWNLOAD: true
PLUGIN_EXPORT_DIR: options/locale/
PLUGIN_IGNORE_BRANCH: true
PLUGIN_PROJECT_IDENTIFIER: gitea
- name: update locales - name: update locales
run: ./build/update-locales.sh run: ./build/update-locales.sh
- 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.2
with: with:
author_email: "teabot@gitea.io" author_email: "teabot@gitea.io"
author_name: GiteaBot author_name: GiteaBot
@ -36,3 +31,19 @@ jobs:
commit_message: "[skip ci] Updated translations via Crowdin" commit_message: "[skip ci] Updated translations via Crowdin"
remote: "git@github.com:go-gitea/gitea.git" remote: "git@github.com:go-gitea/gitea.git"
ssh_key: ${{ secrets.DEPLOY_KEY }} ssh_key: ${{ secrets.DEPLOY_KEY }}
crowdin-push:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea'
steps:
- uses: actions/checkout@v3
- name: push translations to crowdin
uses: docker://jonasfranz/crowdin
env:
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
PLUGIN_UPLOAD: true
PLUGIN_EXPORT_DIR: options/locale/
PLUGIN_IGNORE_BRANCH: true
PLUGIN_PROJECT_IDENTIFIER: gitea
PLUGIN_FILES: |
locale_en-US.ini: options/locale/locale_en-US.ini
PLUGIN_BRANCH: main

View file

@ -4,98 +4,50 @@ on:
workflow_call: workflow_call:
outputs: outputs:
backend: backend:
description: "whether backend files changed"
value: ${{ jobs.detect.outputs.backend }} value: ${{ jobs.detect.outputs.backend }}
frontend: frontend:
description: "whether frontend files changed"
value: ${{ jobs.detect.outputs.frontend }} value: ${{ jobs.detect.outputs.frontend }}
docs: docs:
description: "whether docs files changed"
value: ${{ jobs.detect.outputs.docs }} value: ${{ jobs.detect.outputs.docs }}
actions: actions:
description: "whether actions files changed"
value: ${{ jobs.detect.outputs.actions }} value: ${{ jobs.detect.outputs.actions }}
templates:
value: ${{ jobs.detect.outputs.templates }}
docker:
value: ${{ jobs.detect.outputs.docker }}
swagger:
value: ${{ jobs.detect.outputs.swagger }}
yaml:
value: ${{ jobs.detect.outputs.yaml }}
jobs: jobs:
detect: detect:
name: detect which files changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 3 timeout-minutes: 3
# Map a step output to a job output
outputs: outputs:
backend: ${{ steps.changes.outputs.backend }} backend: ${{ steps.changes.outputs.backend }}
frontend: ${{ steps.changes.outputs.frontend }} frontend: ${{ steps.changes.outputs.frontend }}
docs: ${{ steps.changes.outputs.docs }} docs: ${{ steps.changes.outputs.docs }}
actions: ${{ steps.changes.outputs.actions }} actions: ${{ steps.changes.outputs.actions }}
templates: ${{ steps.changes.outputs.templates }}
docker: ${{ steps.changes.outputs.docker }}
swagger: ${{ steps.changes.outputs.swagger }}
yaml: ${{ steps.changes.outputs.yaml }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: dorny/paths-filter@v3 - uses: dorny/paths-filter@v2
id: changes id: changes
with: with:
filters: | filters: |
backend: backend:
- "**/*.go" - "**/*.go"
- "templates/**/*.tmpl" - "**/*.tmpl"
- "assets/emoji.json"
- "go.mod" - "go.mod"
- "go.sum" - "go.sum"
- "Makefile"
- ".golangci.yml"
- ".editorconfig"
- "options/locale/locale_en-US.ini"
frontend: frontend:
- "*.js" - "**/*.js"
- "*.ts"
- "web_src/**" - "web_src/**"
- "tools/*.js"
- "tools/*.ts"
- "assets/emoji.json"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"
- "Makefile"
- ".eslintrc.cjs"
- ".npmrc"
docs: docs:
- "**/*.md" - "**/*.md"
- ".markdownlint.yaml" - "docs/**"
- "package.json"
- "package-lock.json"
actions: actions:
- ".github/workflows/*" - ".github/workflows/*"
- "Makefile"
templates:
- "tools/lint-templates-*.js"
- "templates/**/*.tmpl"
- "pyproject.toml"
- "poetry.lock"
docker:
- "Dockerfile"
- "Dockerfile.rootless"
- "docker/**"
- "Makefile"
swagger:
- "templates/swagger/v1_json.tmpl"
- "templates/swagger/v1_input.json"
- "Makefile"
- "package.json"
- "package-lock.json"
- ".spectral.yaml"
yaml:
- "**/*.yml"
- "**/*.yaml"
- ".yamllint.yaml"
- "pyproject.toml"
- "poetry.lock"

View file

@ -16,86 +16,28 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make lint-backend - run: make lint-backend
env: env:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
lint-templates:
if: needs.files-changed.outputs.templates == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: pip install poetry
- run: make deps-py
- run: make deps-frontend
- run: make lint-templates
lint-yaml:
if: needs.files-changed.outputs.yaml == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install poetry
- run: make deps-py
- run: make lint-yaml
lint-swagger:
if: needs.files-changed.outputs.swagger == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend
- run: make lint-swagger
lint-spell:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
- run: make lint-spell
lint-go-windows: lint-go-windows:
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
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
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
@ -106,10 +48,10 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make lint-go - run: make lint-go
@ -121,10 +63,10 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs - run: make --always-make checks-backend # ensure the "go-licenses" make target runs
@ -134,16 +76,13 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: 24 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-frontend - run: make lint-frontend
- run: make checks-frontend - run: make checks-frontend
- run: make test-frontend
- run: make frontend - run: make frontend
backend: backend:
@ -151,14 +90,14 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
# no frontend build here as backend should be able to build # no frontend build here as backend should be able to build
# even without any frontend files # even without any frontend files
- run: make deps-backend - run: make deps-backend deps-tools
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag - run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
- name: build-backend-arm64 - name: build-backend-arm64
run: make backend # test cross compile run: make backend # test cross compile
@ -183,23 +122,19 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: 24 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend - run: make deps-frontend
- run: make lint-md - run: make lint-md
- run: make docs # test if build could succeed
actions: actions:
if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with:
go-version-file: go.mod
check-latest: true
- run: make lint-actions - run: make lint-actions

View file

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
pgsql: pgsql:
image: postgres:14 image: postgres:15
env: env:
POSTGRES_DB: test POSTGRES_DB: test
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@ -31,28 +31,24 @@ jobs:
minio: minio:
# as github actions doesn't support "entrypoint", we need to use a non-official image # as github actions doesn't support "entrypoint", we need to use a non-official image
# that has a custom entrypoint set to "minio server /data" # that has a custom entrypoint set to "minio server /data"
image: bitnami/minio:2023.8.31 image: bitnami/minio:2021.3.17
env: env:
MINIO_ROOT_USER: 123456 MINIO_ACCESS_KEY: 123456
MINIO_ROOT_PASSWORD: 12345678 MINIO_SECRET_KEY: 12345678
ports: ports:
- "9000:9000" - "9000:9000"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20.0"
check-latest: true
- name: Add hosts to /etc/hosts - name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts' run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
TAGS: bindata TAGS: bindata
- name: run migration tests - run: make test-pgsql-migration test-pgsql
run: make test-pgsql-migration
- name: run tests
run: make test-pgsql
timeout-minutes: 50 timeout-minutes: 50
env: env:
TAGS: bindata gogit TAGS: bindata gogit
@ -66,19 +62,15 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20.0"
check-latest: true
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit sqlite sqlite_unlock_notify
- name: run migration tests - run: make test-sqlite-migration test-sqlite
run: make test-sqlite-migration
- name: run tests
run: make test-sqlite
timeout-minutes: 50 timeout-minutes: 50
env: env:
TAGS: bindata gogit sqlite sqlite_unlock_notify TAGS: bindata gogit sqlite sqlite_unlock_notify
@ -91,18 +83,26 @@ jobs:
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: test
ports:
- "3306:3306"
elasticsearch: elasticsearch:
image: elasticsearch:7.5.0 image: elasticsearch:7.5.0
env: env:
discovery.type: single-node discovery.type: single-node
ports: ports:
- "9200:9200" - "9200:9200"
meilisearch: smtpimap:
image: getmeili/meilisearch:v1 image: tabascoterrier/docker-imap-devel:latest
env:
MEILI_ENV: development # disable auth
ports: ports:
- "7700:7700" - "25:25"
- "143:143"
- "587:587"
- "993:993"
redis: redis:
image: redis image: redis
options: >- # wait until redis has started options: >- # wait until redis has started
@ -119,18 +119,13 @@ jobs:
MINIO_SECRET_KEY: 12345678 MINIO_SECRET_KEY: 12345678
ports: ports:
- "9000:9000" - "9000:9000"
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
image: mcr.microsoft.com/azure-storage/azurite:latest
ports:
- 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20.0"
check-latest: true
- name: Add hosts to /etc/hosts - name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts' run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
@ -148,21 +143,18 @@ jobs:
RACE_ENABLED: true RACE_ENABLED: true
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }} GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
test-mysql: test-mysql5:
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
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
mysql: mysql:
# the bitnami mysql image has more options than the official one, it's easier to customize image: mysql:5.7
image: bitnami/mysql:8.0
env: env:
ALLOW_EMPTY_PASSWORD: true MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea MYSQL_DATABASE: test
ports: ports:
- "3306:3306" - "3306:3306"
options: >-
--mount type=tmpfs,destination=/bitnami/mysql/data
elasticsearch: elasticsearch:
image: elasticsearch:7.5.0 image: elasticsearch:7.5.0
env: env:
@ -177,60 +169,78 @@ jobs:
- "587:587" - "587:587"
- "993:993" - "993:993"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20.0"
check-latest: true
- name: Add hosts to /etc/hosts - name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts' run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
TAGS: bindata TAGS: bindata
- name: run migration tests
run: make test-mysql-migration
- name: run tests - name: run tests
# run: make integration-test-coverage (at the moment, no coverage is really handled) run: make test-mysql-migration integration-test-coverage
run: make test-mysql
env: env:
TAGS: bindata TAGS: bindata
RACE_ENABLED: true RACE_ENABLED: true
USE_REPO_TEST_DIR: 1 USE_REPO_TEST_DIR: 1
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200" TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
test-mysql8:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
services:
mysql8:
image: mysql:8
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea
ports:
- "3306:3306"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: ">=1.20.0"
- name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql8" | sudo tee -a /etc/hosts'
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- run: make test-mysql8-migration test-mysql8
timeout-minutes: 50
env:
TAGS: bindata
USE_REPO_TEST_DIR: 1
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
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
mssql: mssql:
image: mcr.microsoft.com/mssql/server:2019-latest image: mcr.microsoft.com/mssql/server:latest
env: env:
ACCEPT_EULA: Y ACCEPT_EULA: Y
MSSQL_PID: Standard MSSQL_PID: Standard
SA_PASSWORD: MwantsaSecurePassword1 SA_PASSWORD: MwantsaSecurePassword1
ports: ports:
- "1433:1433" - "1433:1433"
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
image: mcr.microsoft.com/azure-storage/azurite:latest
ports:
- 10000:10000
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20.0"
check-latest: true
- name: Add hosts to /etc/hosts - name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts' run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql" | sudo tee -a /etc/hosts'
- run: make deps-backend - run: make deps-backend
- run: make backend - run: make backend
env: env:
TAGS: bindata TAGS: bindata
- run: make test-mssql-migration - run: make test-mssql-migration test-mssql
- name: run tests
run: make test-mssql
timeout-minutes: 50 timeout-minutes: 50
env: env:
TAGS: bindata TAGS: bindata

View file

@ -11,25 +11,13 @@ jobs:
files-changed: files-changed:
uses: ./.github/workflows/files-changed.yml uses: ./.github/workflows/files-changed.yml
regular: docker-dryrun:
if: needs.files-changed.outputs.docker == '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'
needs: files-changed needs: files-changed
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v2
- uses: docker/build-push-action@v5 - uses: docker/build-push-action@v4
with: with:
push: false push: false
tags: gitea/gitea:linux-amd64 tags: gitea/gitea:linux-amd64
rootless:
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
push: false
file: Dockerfile.rootless
tags: gitea/gitea:linux-amd64

View file

@ -12,22 +12,18 @@ 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:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: 24 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend frontend deps-backend - run: make deps-frontend frontend deps-backend
- run: npx playwright install --with-deps - run: npx playwright install --with-deps
- run: make test-e2e-sqlite - run: make test-e2e-sqlite

View file

@ -1,20 +0,0 @@
name: labeler
on:
pull_request_target:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
labeler:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@v5
with:
sync-labels: true

View file

@ -1,30 +1,24 @@
name: release-nightly name: release-nightly-assets
on: on:
push: push:
branches: [main, release/v*] branches: [ main, release/v* ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
nightly-binary: nightly-binary:
runs-on: namespace-profile-gitea-release-binary runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5 - uses: actions/setup-go@v4
with: with:
go-version-file: go.mod go-version: ">=1.20"
check-latest: true check-latest: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: 24 node-version: 20
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend - run: make deps-frontend deps-backend
# xgo build # xgo build
- run: make release - run: make release
@ -32,7 +26,7 @@ jobs:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key - name: import gpg key
id: import_gpg id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6 uses: crazy-max/ghaction-import-gpg@v5
with: with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }} gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
@ -47,31 +41,25 @@ jobs:
run: | run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}" echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3 - name: upload binaries to s3
run: | uses: jakejarvis/s3-sync-action@master
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress env:
nightly-docker-rootful: AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
runs-on: namespace-profile-gitea-release-docker AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
permissions: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
packages: write # to publish to ghcr.io AWS_REGION: ${{ secrets.AWS_REGION }}
SOURCE_DIR: dist/release
DEST_DIR: gitea/${{ steps.clean_name.outputs.branch }}
nightly-docker:
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
# fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force - run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5 - uses: docker/setup-qemu-action@v2
with: - uses: docker/setup-buildx-action@v2
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name - name: Get cleaned branch name
id: clean_name id: clean_name
run: | run: |
@ -83,72 +71,22 @@ jobs:
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootful docker image - name: build rootful docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v4
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64,linux/riscv64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: |- tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
gitea/gitea:${{ steps.clean_name.outputs.branch }}
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}
nightly-docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Get cleaned branch name
id: clean_name
run: |
# if main then say nightly otherwise cleanup name
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "branch=nightly" >> "$GITHUB_OUTPUT"
exit 0
fi
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fetch go modules
run: make vendor
- name: build rootless docker image - name: build rootless docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v4
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
file: Dockerfile.rootless file: Dockerfile.rootless
tags: |- tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless
ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless

View file

@ -1,154 +0,0 @@
name: release-tag-rc
on:
push:
tags:
- "v1*-rc*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
binary:
runs-on: namespace-profile-gitea-release-binary
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
# xgo build
- run: make release
env:
TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: sign binaries
run: |
for f in dist/release/*; do
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
done
# clean branch name to get the folder name in S3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
with:
gh-cli-version: 2.39.1
- name: create github release
run: |
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
flavor: |
latest=false
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
latest=false
suffix=-rootless
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -1,165 +0,0 @@
name: release-tag-version
on:
push:
tags:
- "v1.*"
- "!v1*-rc*"
- "!v1*-dev"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
binary:
runs-on: namespace-profile-gitea-release-binary
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
check-latest: true
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: package-lock.json
- run: make deps-frontend deps-backend
# xgo build
- run: make release
env:
TAGS: bindata sqlite sqlite_unlock_notify
- name: import gpg key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: sign binaries
run: |
for f in dist/release/*; do
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
done
# clean branch name to get the folder name in S3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
with:
gh-cli-version: 2.39.1
- name: create github release
run: |
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
docker-rootful:
runs-on: namespace-profile-gitea-release-docker
permissions:
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# this will generate tags in the following format:
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootful docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
docker-rootless:
runs-on: namespace-profile-gitea-release-docker
steps:
- uses: actions/checkout@v4
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/metadata-action@v5
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless,onlatest=true
# this will generate tags in the following format (with -rootless suffix added):
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build rootless docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

54
.gitignore vendored
View file

@ -9,21 +9,12 @@ _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_*
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin* __debug_bin
# Visual Studio
/.vs/
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c
@ -36,16 +27,19 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
*.tsbuildinfo
*coverage.out *coverage.out
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
@ -63,7 +57,7 @@ cpu.out
/data /data
/indexers /indexers
/log /log
/public/assets/img/avatar /public/img/avatar
/tests/integration/gitea-integration-* /tests/integration/gitea-integration-*
/tests/integration/indexers-* /tests/integration/indexers-*
/tests/e2e/gitea-e2e-* /tests/e2e/gitea-e2e-*
@ -74,24 +68,31 @@ cpu.out
/tests/*.ini /tests/*.ini
/tests/**/*.git/**/*.sample /tests/**/*.git/**/*.sample
/node_modules /node_modules
/.venv
/yarn.lock /yarn.lock
/yarn-error.log /yarn-error.log
/npm-debug.log* /npm-debug.log*
/public/assets/js /public/js
/public/assets/css /public/css
/public/assets/fonts /public/fonts
/public/assets/licenses.txt /public/img/webpack
/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
# Files and folders that were previously generated
/public/assets/img/webpack
# Snapcraft # Snapcraft
/gitea_a*.txt
snap/.snapcraft/ snap/.snapcraft/
parts/ parts/
stage/ stage/
@ -101,9 +102,6 @@ prime/
*_source.tar.bz2 *_source.tar.bz2
.DS_Store .DS_Store
# nix-direnv generated files
.direnv/
# Make evidence files # Make evidence files
/.make_evidence /.make_evidence

View file

@ -10,19 +10,10 @@ tasks:
- name: Run backend - name: Run backend
command: | command: |
gp sync-await setup gp sync-await setup
if [ ! -f custom/conf/app.ini ]
# Get the URL and extract the domain then
url=$(gp url 3000)
domain=$(echo $url | awk -F[/:] '{print $4}')
if [ -f custom/conf/app.ini ]; then
sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini
sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini
sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini
else
mkdir -p custom/conf/ mkdir -p custom/conf/
echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini echo -e "[server]\nROOT_URL=$(gp url 3000)/" > custom/conf/app.ini
echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini
fi fi
export TAGS="sqlite sqlite_unlock_notify" export TAGS="sqlite sqlite_unlock_notify"
@ -42,9 +33,8 @@ vscode:
- DavidAnson.vscode-markdownlint - DavidAnson.vscode-markdownlint
- Vue.volar - Vue.volar
- ms-azuretools.vscode-docker - ms-azuretools.vscode-docker
- vitest.explorer - zixuanchen.vitest-explorer
- cweijan.vscode-database-client2 - alexcvzz.vscode-sqlite
- GitHub.vscode-pull-request-github
ports: ports:
- name: Gitea - name: Gitea

View file

@ -1,180 +1,131 @@
version: "2"
output:
sort-order:
- file
linters: linters:
default: none
enable: enable:
- bidichk - bidichk
# - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841
- depguard - depguard
- dupl - dupl
- errcheck - errcheck
- forbidigo - forbidigo
- gocritic - gocritic
# - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt
- gofumpt
- gosimple
- govet - govet
- ineffassign - ineffassign
- mirror
- nakedret - nakedret
- nolintlint - nolintlint
- perfsprint
- revive - revive
- staticcheck - staticcheck
- testifylint # - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
- stylecheck
- typecheck
- unconvert - unconvert
- unparam
- unused - unused
- usestdlibvars # - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841
- usetesting
- wastedassign - wastedassign
settings: enable-all: false
depguard: disable-all: true
rules: fast: false
main:
deny: run:
- pkg: encoding/json go: "1.20"
desc: use gitea's modules/json instead of encoding/json timeout: 10m
- pkg: github.com/unknwon/com skip-dirs:
desc: use gitea's util and replacements - node_modules
- pkg: io/ioutil - public
desc: use os or io instead - web_src
- pkg: golang.org/x/exp
desc: it's experimental and unreliable linters-settings:
- pkg: code.gitea.io/gitea/modules/git/internal stylecheck:
desc: do not use the internal package, use AddXxx function instead checks: ["all", "-ST1005", "-ST1003"]
- pkg: gopkg.in/ini.v1 nakedret:
desc: do not use the ini package, use gitea's config system instead max-func-lines: 0
- pkg: gitea.com/go-chi/cache gocritic:
desc: do not use the go-chi cache package, use gitea's cache system disabled-checks:
nolintlint: - ifElseChain
allow-unused: false - singleCaseSwitch # Every time this occurred in the code, there was no other way.
require-explanation: true revive:
require-specific: true ignore-generated-header: false
gocritic: severity: warning
disabled-checks: confidence: 0.8
- ifElseChain errorCode: 1
- singleCaseSwitch # Every time this occurred in the code, there was no other way. warningCode: 1
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: rules:
- linters: - name: blank-imports
- dupl - name: context-as-argument
- errcheck - name: context-keys-type
- gocyclo - name: dot-imports
- gosec - name: error-return
- staticcheck - name: error-strings
- unparam - name: error-naming
path: _test\.go - name: exported
- linters: - name: if-return
- dupl - name: increment-decrement
- errcheck - name: var-naming
- gocyclo - name: var-declaration
- gosec - name: package-comments
path: models/migrations/v - name: range
- linters: - name: receiver-naming
- forbidigo - name: time-naming
path: cmd - name: unexported-return
- linters: - name: indent-error-flow
- dupl - name: errorf
text: (?i)webhook - name: duplicated-imports
- linters: - name: modifies-value-receiver
- gocritic gofumpt:
text: (?i)`ID' should not be capitalized extra-rules: true
- linters: lang-version: "1.20"
- deadcode depguard:
- unused list-type: denylist
text: (?i)swagger # Check the list against standard lib.
- linters: include-go-root: true
- staticcheck packages-with-error-message:
text: (?i)argument x is overwritten before first use - encoding/json: "use gitea's modules/json instead of encoding/json"
- linters: - github.com/unknwon/com: "use gitea's util and replacements"
- gocritic - io/ioutil: "use os or io instead"
text: '(?i)commentFormatting: put a space between `//` and comment text' - golang.org/x/exp: "it's experimental and unreliable."
- linters: - code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead"
- gocritic - gopkg.in/ini.v1: "do not use the ini package, use gitea's config system instead"
text: '(?i)exitAfterDefer:'
paths:
- node_modules
- public
- web_src
- third_party$
- builtin$
- examples$
issues: issues:
max-issues-per-linter: 0 max-issues-per-linter: 0
max-same-issues: 0 max-same-issues: 0
formatters: exclude-rules:
enable: # Exclude some linters from running on tests files.
- gofmt - path: _test\.go
- gofumpt linters:
settings: - gocyclo
gofumpt: - errcheck
extra-rules: true - dupl
exclusions: - gosec
generated: lax - unparam
paths: - staticcheck
- node_modules - path: models/migrations/v
- public linters:
- web_src - gocyclo
- third_party$ - errcheck
- builtin$ - dupl
- examples$ - gosec
- path: cmd
run: linters:
timeout: 10m - forbidigo
- linters:
- dupl
text: "webhook"
- linters:
- gocritic
text: "`ID' should not be capitalized"
- linters:
- unused
- deadcode
text: "swagger"
- linters:
- staticcheck
text: "argument x is overwritten before first use"
- text: "commentFormatting: put a space between `//` and comment text"
linters:
- gocritic
- text: "exitAfterDefer:"
linters:
- gocritic

View file

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

View file

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

View file

@ -1,15 +1,18 @@
commands-show-output: false commands-show-output: false
fenced-code-language: false fenced-code-language: false
first-line-h1: false first-line-h1: false
heading-increment: false header-increment: false
line-length: {code_blocks: false, tables: false, stern: true, line_length: -1} line-length: {code_blocks: false, tables: false, stern: true, line_length: -1}
no-alt-text: false no-alt-text: false
no-bare-urls: false no-bare-urls: false
no-emphasis-as-heading: false no-blanks-blockquote: false
no-duplicate-header: {allow_different_nesting: true}
no-emphasis-as-header: false
no-empty-links: false no-empty-links: false
no-hard-tabs: {code_blocks: false} no-hard-tabs: {code_blocks: false}
no-inline-html: false no-inline-html: false
no-space-in-code: false no-space-in-code: false
no-space-in-emphasis: false no-space-in-emphasis: false
no-trailing-punctuation: false
no-trailing-spaces: {br_spaces: 0} no-trailing-spaces: {br_spaces: 0}
single-h1: false single-h1: false

141
.stylelintrc.yaml Normal file
View file

@ -0,0 +1,141 @@
plugins:
- stylelint-declaration-strict-value
ignoreFiles:
- "**/*.go"
overrides:
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console.css", "font_i18n.css"]
rules:
scale-unlimited/declaration-strict-value: null
- files: ["**/chroma/*", "**/codemirror/*"]
rules:
block-no-empty: null
- files: ["**/*.vue"]
customSyntax: postcss-html
rules:
alpha-value-notation: null
annotation-no-unknown: true
at-rule-allowed-list: null
at-rule-disallowed-list: null
at-rule-empty-line-before: null
at-rule-no-unknown: true
at-rule-no-vendor-prefix: true
at-rule-property-required-list: null
block-no-empty: true
color-function-notation: null
color-hex-alpha: null
color-hex-length: null
color-named: null
color-no-hex: null
color-no-invalid-hex: true
comment-empty-line-before: null
comment-no-empty: true
comment-pattern: null
comment-whitespace-inside: null
comment-word-disallowed-list: null
custom-media-pattern: null
custom-property-empty-line-before: null
custom-property-no-missing-var-function: true
custom-property-pattern: null
declaration-block-no-duplicate-custom-properties: true
declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}]
declaration-block-no-redundant-longhand-properties: null
declaration-block-no-shorthand-property-overrides: null
declaration-block-single-line-max-declarations: null
declaration-empty-line-before: null
declaration-no-important: null
declaration-property-max-values: null
declaration-property-unit-allowed-list: null
declaration-property-unit-disallowed-list: null
declaration-property-value-allowed-list: null
declaration-property-value-disallowed-list: null
declaration-property-value-no-unknown: true
font-family-name-quotes: always-where-recommended
font-family-no-duplicate-names: true
font-family-no-missing-generic-family-keyword: true
font-weight-notation: null
function-allowed-list: null
function-calc-no-unspaced-operator: true
function-disallowed-list: null
function-linear-gradient-no-nonstandard-direction: true
function-name-case: lower
function-no-unknown: null
function-url-no-scheme-relative: null
function-url-quotes: always
function-url-scheme-allowed-list: null
function-url-scheme-disallowed-list: null
hue-degree-notation: null
import-notation: string
keyframe-block-no-duplicate-selectors: true
keyframe-declaration-no-important: true
keyframe-selector-notation: null
keyframes-name-pattern: null
length-zero-no-unit: true
max-nesting-depth: null
media-feature-name-allowed-list: null
media-feature-name-disallowed-list: null
media-feature-name-no-unknown: true
media-feature-name-no-vendor-prefix: true
media-feature-name-unit-allowed-list: null
media-feature-name-value-allowed-list: null
media-feature-name-value-no-unknown: true
media-feature-range-notation: null
named-grid-areas-no-invalid: true
no-descending-specificity: null
no-duplicate-at-import-rules: true
no-duplicate-selectors: true
no-empty-source: true
no-invalid-double-slash-comments: true
no-invalid-position-at-import-rule: null
no-irregular-whitespace: true
no-unknown-animations: null
no-unknown-custom-properties: null
number-max-precision: null
property-allowed-list: null
property-disallowed-list: null
property-no-unknown: true
property-no-vendor-prefix: null
rule-empty-line-before: null
rule-selector-property-disallowed-list: null
scale-unlimited/declaration-strict-value: [[color, background-color, border-color, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true}]
selector-attribute-name-disallowed-list: null
selector-attribute-operator-allowed-list: null
selector-attribute-operator-disallowed-list: null
selector-attribute-quotes: always
selector-class-pattern: null
selector-combinator-allowed-list: null
selector-combinator-disallowed-list: null
selector-disallowed-list: null
selector-id-pattern: null
selector-max-attribute: null
selector-max-class: null
selector-max-combinators: null
selector-max-compound-selectors: null
selector-max-id: null
selector-max-pseudo-class: null
selector-max-specificity: null
selector-max-type: null
selector-max-universal: null
selector-nested-pattern: null
selector-no-qualifying-type: null
selector-no-vendor-prefix: true
selector-not-notation: null
selector-pseudo-class-allowed-list: null
selector-pseudo-class-disallowed-list: null
selector-pseudo-class-no-unknown: true
selector-pseudo-element-allowed-list: null
selector-pseudo-element-colon-notation: double
selector-pseudo-element-disallowed-list: null
selector-pseudo-element-no-unknown: true
selector-type-case: lower
selector-type-no-unknown: [true, {ignore: [custom-elements]}]
shorthand-property-no-redundant-values: true
string-no-newline: true
time-min-milliseconds: null
unit-allowed-list: null
unit-disallowed-list: null
unit-no-unknown: true
value-keyword-case: null
value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}]

View file

@ -1,44 +0,0 @@
extends: default
rules:
braces:
min-spaces-inside: 0
max-spaces-inside: 1
min-spaces-inside-empty: 0
max-spaces-inside-empty: 0
brackets:
min-spaces-inside: 0
max-spaces-inside: 1
min-spaces-inside-empty: 0
max-spaces-inside-empty: 0
comments:
require-starting-space: true
ignore-shebangs: true
min-spaces-from-content: 1
comments-indentation:
level: error
document-start:
level: error
present: false
document-end:
present: false
empty-lines:
max: 1
indentation:
spaces: 2
line-length: disable
truthy:
allowed-values: ["true", "false", "on", "off"]
ignore: |
.venv
node_modules

View file

@ -42,13 +42,13 @@ GARGS = "--no-print-directory"
# The GNU convention is to use the lowercased `prefix` variable/macro to # The GNU convention is to use the lowercased `prefix` variable/macro to
# specify the installation directory. Humor them. # specify the installation directory. Humor them.
GPREFIX = GPREFIX = ""
.if defined(PREFIX) && ! defined(prefix) .if defined(PREFIX) && ! defined(prefix)
GPREFIX = 'prefix = "$(PREFIX)"' GPREFIX = 'prefix = "$(PREFIX)"'
.endif .endif
.BEGIN: .SILENT .BEGIN: .SILENT
which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) which $(GMAKE) || printf "Error: GNU Make is required!\n\n" 1>&2 && false
.PHONY: FRC .PHONY: FRC
$(.TARGETS): FRC $(.TARGETS): FRC

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@
- [How to report issues](#how-to-report-issues) - [How to report issues](#how-to-report-issues)
- [Types of issues](#types-of-issues) - [Types of issues](#types-of-issues)
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation) - [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
- [Issue locking](#issue-locking)
- [Building Gitea](#building-gitea) - [Building Gitea](#building-gitea)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Backend](#backend) - [Backend](#backend)
@ -48,7 +47,6 @@
- [Release Cycle](#release-cycle) - [Release Cycle](#release-cycle)
- [Maintainers](#maintainers) - [Maintainers](#maintainers)
- [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc) - [Technical Oversight Committee (TOC)](#technical-oversight-committee-toc)
- [TOC election process](#toc-election-process)
- [Current TOC members](#current-toc-members) - [Current TOC members](#current-toc-members)
- [Previous TOC/owners members](#previous-tocowners-members) - [Previous TOC/owners members](#previous-tocowners-members)
- [Governance Compensation](#governance-compensation) - [Governance Compensation](#governance-compensation)
@ -62,7 +60,7 @@
## Introduction ## Introduction
This document explains how to contribute changes to the Gitea project. \ This document explains how to contribute changes to the Gitea project. \
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \ It assumes you have followed the [installation instructions](https://docs.gitea.io/en-us/). \
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io). Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/). For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
@ -77,7 +75,7 @@ If your issue has not been reported yet, [open an issue](https://github.com/go-g
and answer the questions so we can understand and reproduce the problematic behavior. \ and answer the questions so we can understand and reproduce the problematic behavior. \
Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \ Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \
The more detailed and specific you are, the faster we can fix the issue. \ The more detailed and specific you are, the faster we can fix the issue. \
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \ It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://try.gitea.io>, as perhaps your problem has already been fixed on a current version. \
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report. Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
Please be kind, remember that Gitea comes at no cost to you, and you're getting free help. Please be kind, remember that Gitea comes at no cost to you, and you're getting free help.
@ -104,13 +102,6 @@ the goals for the project and tools.
Pull requests should not be the place for architecture discussions. Pull requests should not be the place for architecture discussions.
### Issue locking
Commenting on closed or merged issues/PRs is strongly discouraged.
Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
## Building Gitea ## Building Gitea
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea). See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
@ -119,7 +110,7 @@ See the [development setup instructions](https://docs.gitea.com/development/hack
### Backend ### Backend
Go dependencies are managed using [Go Modules](https://go.dev/cmd/go/#hdr-Module_maintenance). \ Go dependencies are managed using [Go Modules](https://golang.org/cmd/go/#hdr-Module_maintenance). \
You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules). You can find more details in the [go mod documentation](https://go.dev/ref/mod) and the [Go Modules Wiki](https://github.com/golang/go/wiki/Modules).
Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \ Pull requests should only modify `go.mod` and `go.sum` where it is related to your change, be it a bugfix or a new feature. \
@ -176,13 +167,13 @@ Here's how to run the test suite:
| Command | Action | | | Command | Action | |
| :------------------------------------- | :----------------------------------------------- | ------------ | | :------------------------------------- | :----------------------------------------------- | ------------ |
|``make test[\#SpecificTestName]`` | run unit test(s) | | |``make test[\#SpecificTestName]`` | run unit test(s) |
|``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) | |``make test-sqlite[\#SpecificTestName]``| run [integration](tests/integration) test(s) for SQLite |[More details](tests/integration/README.md) |
|``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) | |``make test-e2e-sqlite[\#SpecificTestName]``| run [end-to-end](tests/e2e) test(s) for SQLite |[More details](tests/e2e/README.md) |
## 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. \
@ -212,20 +203,10 @@ Some of the key points:
In the PR title, describe the problem you are fixing, not how you are fixing it. \ In the PR title, describe the problem you are fixing, not how you are fixing it. \
Use the first comment as a summary of your PR. \ Use the first comment as a summary of your PR. \
In the PR summary, you can describe exactly how you are fixing this problem. In the PR summary, you can describe exactly how you are fixing this problem. \
Keep this summary up-to-date as the PR evolves. \ Keep this summary up-to-date as the PR evolves. \
If your PR changes the UI, you must add **after** screenshots in the PR summary. \ If your PR changes the UI, you must add **after** screenshots in the PR summary. \
If you are not implementing a new feature, you should also post **before** screenshots for comparison. If you are not implementing a new feature, you should also post **before** screenshots for comparison. \
If you are implementing a new feature, your PR will only be merged if your screenshots are up to date.\
Furthermore, feature PRs will only be merged if their summary contains a clear usage description (understandable for users) and testing description (understandable for reviewers).
You should strive to combine both into a single description.
Another requirement for merging PRs is that the PR is labeled correctly.\
However, this is not your job as a contributor, but the job of the person merging your PR.\
If you think that your PR was labeled incorrectly, or notice that it was merged without labels, please let us know.
If your PR closes some issues, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like If your PR closes some issues, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like
```text ```text
@ -244,20 +225,17 @@ PRs without a milestone may not be merged.
### Labels ### Labels
Almost all labels used inside Gitea can be classified as one of the following: Every PR should be labeled correctly with every label that applies. \
This includes especially the distinction between `bug` (fixing existing functionality), `feature` (new functionality), `enhancement` (upgrades for existing functionality), and `refactoring` (improving the internal code structure without changing the output (much)). \
- `modifies/…`: Determines which parts of the codebase are affected. These labels will be set through the CI. Furthermore,
- `topic/…`: Determines the conceptual component of Gitea that is affected, i.e. issues, projects, or authentication. At best, PRs should only target one component but there might be overlap. Must be set manually.
- `type/…`: Determines the type of an issue or PR (feature, refactoring, docs, bug, …). If GitHub supported scoped labels, these labels would be exclusive, so you should set **exactly** one, not more or less (every PR should fall into one of the provided categories, and only one).
- `issue/…` / `pr/…`: Labels that are specific to issues or PRs respectively and that are only necessary in a given context, i.e. `issue/not-a-bug` or `pr/need-2-approvals`
Every PR should be labeled correctly with every label that applies.
There are also some labels that will be managed automatically.\
In particular, these are
- the amount of pending required approvals - the amount of pending required approvals
- has all `backport`s or needs a manual backport - whether this PR is `blocked`, a `backport` or `breaking`
- if it targets the `ui` or `api`
- if it increases the application `speed`
- reduces `memory usage`
are oftentimes notable labels.
### Breaking PRs ### Breaking PRs
@ -274,16 +252,13 @@ Changing the default value of a setting or replacing the setting with another on
#### How to handle breaking PRs? #### How to handle breaking PRs?
If your PR has a breaking change, you must add two things to the summary of your PR: If your PR has a breaking change, you must add a `BREAKING` section to your PR summary, e.g.
1. A reasoning why this breaking change is necessary ```
2. A `BREAKING` section explaining in simple terms (understandable for a typical user) how this PR affects users and how to mitigate these changes. This section can look for example like
```md
## :warning: BREAKING :warning: ## :warning: BREAKING :warning:
``` ```
Breaking PRs will not be merged as long as not both of these requirements are met. To explain how this will affect users and how to mitigate these changes.
### Maintaining open PRs ### Maintaining open PRs
@ -358,12 +333,11 @@ $REWRITTEN_PR_SUMMARY
## Documentation ## Documentation
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs). If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in the same PR.
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
## API v1 ## API v1
The API is documented by [swagger](https://gitea.com/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest). The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest).
### GitHub API compatibility ### GitHub API compatibility
@ -465,7 +439,7 @@ We assume in good faith that the information you provide is legally binding.
We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. \ We adopted a release schedule to streamline the process of working on, finishing, and issuing releases. \
The overall goal is to make a major release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. \ The overall goal is to make a major release every three or four months, which breaks down into two or three months of general development followed by one month of testing and polishing known as the release freeze. \
All the feature pull requests should be All the feature pull requests should be
merged before feature freeze. All feature pull requests haven't been merged before this feature freeze will be moved to next milestone, please notice our feature freeze announcement on discord. And, during the frozen period, a corresponding merged before feature freeze. And, during the frozen period, a corresponding
release branch is open for fixes backported from main branch. Release candidates release branch is open for fixes backported from main branch. Release candidates
are made during this period for user testing to are made during this period for user testing to
obtain a final version that is maintained in this branch. obtain a final version that is maintained in this branch.
@ -496,53 +470,36 @@ if possible provide GPG signed commits.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
https://help.github.com/articles/signing-commits-with-gpg/ https://help.github.com/articles/signing-commits-with-gpg/
Furthermore, any account with write access (like bots and TOC members) **must** use 2FA.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
## Technical Oversight Committee (TOC) ## Technical Oversight Committee (TOC)
At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions are elected as it has been over the past years, and the other three consist of appointed members from the Gitea company. At the start of 2023, the `Owners` team was dissolved. Instead, the governance charter proposed a technical oversight committee (TOC) which expands the ownership team of the Gitea project from three elected positions to six positions. Three positions would be elected as it has been over the past years, and the other three would consist of appointed members from the Gitea company.
https://blog.gitea.com/quarterly-23q1/ https://blog.gitea.io/2023/02/gitea-quarterly-report-23q1/
### TOC election process When the new community members have been elected, the old members will give up ownership to the newly elected members. For security reasons, TOC members or any account with write access (like a bot) must use 2FA.
https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/
Any maintainer is eligible to be part of the community TOC if they are not associated with the Gitea company.
A maintainer can either nominate themselves, or can be nominated by other maintainers to be a candidate for the TOC election.
If you are nominated by someone else, you must first accept your nomination before the vote starts to be a candidate.
The TOC is elected for one year, the TOC election happens yearly.
After the announcement of the results of the TOC election, elected members have two weeks time to confirm or refuse the seat.
If an elected member does not answer within this timeframe, they are automatically assumed to refuse the seat.
Refusals result in the person with the next highest vote getting the same choice.
As long as seats are empty in the TOC, members of the previous TOC can fill them until an elected member accepts the seat.
If an elected member that accepts the seat does not have 2FA configured yet, they will be temporarily counted as `answer pending` until they manage to configure 2FA, thus leaving their seat empty for this duration.
### Current TOC members ### Current TOC members
- 2024-01-01 ~ 2024-12-31 - 2023-01-01 ~ 2023-12-31 - https://blog.gitea.io/2023/02/gitea-quarterly-report-23q1/
- Company - Company
- [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com> - [Jason Song](https://gitea.com/wolfogre) <i@wolfogre.com>
- [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com> - [Lunny Xiao](https://gitea.com/lunny) <xiaolunwen@gmail.com>
- [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.com> - [Matti Ranta](https://gitea.com/techknowlogick) <techknowlogick@gitea.io>
- Community - Community
- [6543](https://gitea.com/6543) <6543@obermui.de> - [6543](https://gitea.com/6543) <6543@obermui.de>
- [delvh](https://gitea.com/delvh) <dev.lh@web.de> - [Andrew Thornton](https://gitea.com/zeripath) <art27@cantab.net>
- [John Olheiser](https://gitea.com/jolheiser) <john.olheiser@gmail.com> - [John Olheiser](https://gitea.com/jolheiser) <john.olheiser@gmail.com>
### Previous TOC/owners members ### Previous TOC/owners members
Here's the history of the owners and the time they served: Here's the history of the owners and the time they served:
- [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 - [Lunny Xiao](https://gitea.com/lunny) - 2016, 2017, [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
- [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017 - [Kim Carlbäcker](https://github.com/bkcsoft) - 2016, 2017
- [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017 - [Thomas Boerger](https://gitea.com/tboerger) - 2016, 2017
- [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801) - [Lauris Bukšis-Haberkorns](https://gitea.com/lafriks) - [2018](https://github.com/go-gitea/gitea/issues/3255), [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801)
- [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 - [Matti Ranta](https://gitea.com/techknowlogick) - [2019](https://github.com/go-gitea/gitea/issues/5572), [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
- [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872), 2023 - [Andrew Thornton](https://gitea.com/zeripath) - [2020](https://github.com/go-gitea/gitea/issues/9230), [2021](https://github.com/go-gitea/gitea/issues/13801), [2022](https://github.com/go-gitea/gitea/issues/17872)
- [6543](https://gitea.com/6543) - 2023
- [John Olheiser](https://gitea.com/jolheiser) - 2023
- [Jason Song](https://gitea.com/wolfogre) - 2023
## Governance Compensation ## Governance Compensation

View file

@ -1,47 +1,29 @@
# Build stage #Build stage
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env FROM docker.io/library/golang:1.20-alpine3.18 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS="bindata timetzdata $TAGS" ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
# Build deps #Build deps
RUN apk --no-cache add \ RUN apk --no-cache add build-base git nodejs npm
build-base \
git \
nodejs \
npm \
&& rm -rf /var/cache/apk/*
# Setup repo #Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
# Checkout version if set #Checkout version if set
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build && make clean-all build
# Begin env-to-ini build # Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go RUN go build contrib/environment-to-ini/environment-to-ini.go
# Copy local files FROM docker.io/library/alpine:3.18
COPY docker/root /tmp/local
# Set permissions
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/gitea \
/tmp/local/etc/s6/gitea/* \
/tmp/local/etc/s6/openssh/* \
/tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@ -57,8 +39,7 @@ RUN apk --no-cache add \
s6 \ s6 \
sqlite \ sqlite \
su-exec \ su-exec \
gnupg \ gnupg
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \
@ -72,15 +53,18 @@ RUN addgroup \
git && \ git && \
echo "git:*" | chpasswd -e echo "git:*" | chpasswd -e
ENV USER=git ENV USER git
ENV GITEA_CUSTOM=/data/gitea ENV GITEA_CUSTOM /data/gitea
VOLUME ["/data"] VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/usr/bin/s6-svscan", "/etc/s6"] CMD ["/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local / COPY docker/root /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/*
RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh

View file

@ -1,45 +1,29 @@
# Build stage #Build stage
FROM docker.io/library/golang:1.24-alpine3.22 AS build-env FROM docker.io/library/golang:1.20-alpine3.18 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY ${GOPROXY:-direct}
ARG GITEA_VERSION ARG GITEA_VERSION
ARG TAGS="sqlite sqlite_unlock_notify" ARG TAGS="sqlite sqlite_unlock_notify"
ENV TAGS="bindata timetzdata $TAGS" ENV TAGS "bindata timetzdata $TAGS"
ARG CGO_EXTRA_CFLAGS ARG CGO_EXTRA_CFLAGS
#Build deps #Build deps
RUN apk --no-cache add \ RUN apk --no-cache add build-base git nodejs npm
build-base \
git \
nodejs \
npm \
&& rm -rf /var/cache/apk/*
# Setup repo #Setup repo
COPY . ${GOPATH}/src/code.gitea.io/gitea COPY . ${GOPATH}/src/code.gitea.io/gitea
WORKDIR ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea
# Checkout version if set #Checkout version if set
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build && make clean-all build
# Begin env-to-ini build # Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go RUN go build contrib/environment-to-ini/environment-to-ini.go
# Copy local files FROM docker.io/library/alpine:3.18
COPY docker/rootless /tmp/local
# Set permissions
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/tmp/local/usr/local/bin/docker-setup.sh \
/tmp/local/usr/local/bin/gitea \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.22
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000
@ -51,9 +35,7 @@ RUN apk --no-cache add \
gettext \ gettext \
git \ git \
curl \ curl \
gnupg \ gnupg
openssh-keygen \
&& rm -rf /var/cache/apk/*
RUN addgroup \ RUN addgroup \
-S -g 1000 \ -S -g 1000 \
@ -69,23 +51,26 @@ RUN addgroup \
RUN mkdir -p /var/lib/gitea /etc/gitea RUN mkdir -p /var/lib/gitea /etc/gitea
RUN chown git:git /var/lib/gitea /etc/gitea RUN chown git:git /var/lib/gitea /etc/gitea
COPY --from=build-env /tmp/local / COPY docker/rootless /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-setup.sh /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini
RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh
# git:git #git:git
USER 1000:1000 USER 1000:1000
ENV GITEA_WORK_DIR=/var/lib/gitea ENV GITEA_WORK_DIR /var/lib/gitea
ENV GITEA_CUSTOM=/var/lib/gitea/custom ENV GITEA_CUSTOM /var/lib/gitea/custom
ENV GITEA_TEMP=/tmp/gitea ENV GITEA_TEMP /tmp/gitea
ENV TMPDIR=/tmp/gitea ENV TMPDIR /tmp/gitea
# TODO add to docs the ability to define the ini to load (useful to test and revert a config) #TODO add to docs the ability to define the ini to load (useful to test and revert a config)
ENV GITEA_APP_INI=/etc/gitea/app.ini ENV GITEA_APP_INI /etc/gitea/app.ini
ENV HOME="/var/lib/gitea/git" ENV HOME "/var/lib/gitea/git"
VOLUME ["/var/lib/gitea", "/etc/gitea"] VOLUME ["/var/lib/gitea", "/etc/gitea"]
WORKDIR /var/lib/gitea WORKDIR /var/lib/gitea
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD [] CMD []

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)
@ -45,23 +46,9 @@ Wim <wim@42.be> (@42wim)
Jason Song <i@wolfogre.com> (@wolfogre) Jason Song <i@wolfogre.com> (@wolfogre)
Yarden Shoham <git@yardenshoham.com> (@yardenshoham) Yarden Shoham <git@yardenshoham.com> (@yardenshoham)
Yu Tian <zettat123@gmail.com> (@Zettat123) Yu Tian <zettat123@gmail.com> (@Zettat123)
Eddie Yang <576951401@qq.com> (@yp05327)
Dong Ge <gedong_1994@163.com> (@sillyguodong) Dong Ge <gedong_1994@163.com> (@sillyguodong)
Xinyi Gong <hestergong@gmail.com> (@HesterG) Xinyi Gong <hestergong@gmail.com> (@HesterG)
wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang) wxiaoguang <wxiaoguang@gmail.com> (@wxiaoguang)
Gary Moon <gary@garymoon.net> (@garymoon) Gary Moon <gary@garymoon.net> (@garymoon)
Philip Peterson <philip.c.peterson@gmail.com> (@philip-peterson) Philip Peterson <philip.c.peterson@gmail.com> (@philip-peterson)
Denys Konovalov <kontakt@denyskon.de> (@denyskon)
Punit Inani <punitinani1@gmail.com> (@puni9869)
CaiCandong <1290147055@qq.com> (@caicandong)
Rui Chen <rui@chenrui.dev> (@chenrui333)
Nanguan Lin <nanguanlin6@gmail.com> (@lng2020)
kerwin612 <kerwin612@qq.com> (@kerwin612)
Gary Wang <git@blumia.net> (@BLumia)
Tim-Niclas Oelschläger <zokki.softwareschmiede@gmail.com> (@zokkis)
Yu Liu <1240335630@qq.com> (@HEREYUA)
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
Rowan Bohde <rowan.bohde@gmail.com> (@bohde)
hiifong <i@hiif.ong> (@hiifong)
metiftikci <metiftikci@hotmail.com> (@metiftikci)
Christopher Homberger <christopher.homberger@web.de> (@ChristopherHX)
Tobias Balle-Petersen <tobiasbp@gmail.com> (@tobiasbp)

531
Makefile
View file

@ -23,39 +23,36 @@ 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.20.x
AIR_PACKAGE ?= github.com/air-verse/air@v1 AIR_PACKAGE ?= github.com/cosmtrek/air@v1.43.0
EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.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.52.2
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12 GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.32.3 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.4
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.6.0
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@latest
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@latest
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.19.1
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
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
GOPATH ?= $(shell $(GO) env GOPATH)
export PATH := $(GOPATH)/bin:$(PATH)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766 CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS) CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
endif endif
ifeq ($(GOOS),windows) ifeq ($(OS), Windows_NT)
IS_WINDOWS := yes GOFLAGS := -v -buildmode=exe
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows) EXECUTABLE ?= gitea.exe
ifeq ($(GOOS),) else ifeq ($(OS), Windows)
IS_WINDOWS := yes
endif
endif
ifeq ($(IS_WINDOWS),yes)
GOFLAGS := -v -buildmode=exe GOFLAGS := -v -buildmode=exe
EXECUTABLE ?= gitea.exe EXECUTABLE ?= gitea.exe
else else
@ -71,26 +68,32 @@ endif
EXTRA_GOFLAGS ?= EXTRA_GOFLAGS ?=
MAKE_VERSION := $(shell "$(MAKE)" -v | cat | head -n 1) MAKE_VERSION := $(shell "$(MAKE)" -v | 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)
# backwards compatible to build with Drone
ifneq ($(DRONE_TAG),)
GITHUB_REF_TYPE := tag
GITHUB_REF_NAME := $(DRONE_TAG)
endif
ifneq ($(GITHUB_REF_TYPE),branch) ifneq ($(GITHUB_REF_TYPE),branch)
VERSION ?= $(subst v,,$(GITHUB_REF_NAME)) VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
GITEA_VERSION ?= $(VERSION) GITEA_VERSION ?= $(VERSION)
else else
ifneq ($(GITHUB_REF_NAME),) ifneq ($(GITHUB_REF_NAME),)
VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))-nightly VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
else else
VERSION ?= main VERSION ?= main
endif endif
@ -110,21 +113,24 @@ endif
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/riscv64 LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
GO_PACKAGES ?= $(filter-out 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/)) 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/...)
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
WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST := public/js/index.js public/css/index.css
WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/img/webpack
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
SVG_DEST_DIR := public/assets/img/svg SVG_DEST_DIR := public/img/svg
AIR_TMP_DIR := .air AIR_TMP_DIR := .air
@ -135,21 +141,22 @@ TAGS ?=
TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS)) TAGS_SPLIT := $(subst $(COMMA), ,$(TAGS))
TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags TAGS_EVIDENCE := $(MAKE_EVIDENCE_DIR)/tags
TEST_TAGS ?= $(TAGS_SPLIT) sqlite sqlite_unlock_notify TEST_TAGS ?= 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
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/*))
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,19 +164,24 @@ 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 \| Safe}}/api/v1"|g
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/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
TEST_MYSQL_USERNAME ?= root TEST_MYSQL_USERNAME ?= root
TEST_MYSQL_PASSWORD ?= TEST_MYSQL_PASSWORD ?=
TEST_MYSQL8_HOST ?= mysql8:3306
TEST_MYSQL8_DBNAME ?= testgitea
TEST_MYSQL8_USERNAME ?= root
TEST_MYSQL8_PASSWORD ?=
TEST_PGSQL_HOST ?= pgsql:5432 TEST_PGSQL_HOST ?= pgsql:5432
TEST_PGSQL_DBNAME ?= testgitea TEST_PGSQL_DBNAME ?= testgitea
TEST_PGSQL_USERNAME ?= postgres TEST_PGSQL_USERNAME ?= postgres
TEST_PGSQL_PASSWORD ?= postgres TEST_PGSQL_PASSWORD ?= postgres
TEST_PGSQL_SCHEMA ?= gtestschema TEST_PGSQL_SCHEMA ?= gtestschema
TEST_MINIO_ENDPOINT ?= minio:9000
TEST_MSSQL_HOST ?= mssql:1433 TEST_MSSQL_HOST ?= mssql:1433
TEST_MSSQL_DBNAME ?= gitea TEST_MSSQL_DBNAME ?= gitea
TEST_MSSQL_USERNAME ?= sa TEST_MSSQL_USERNAME ?= sa
@ -179,11 +191,58 @@ 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 " - 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-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 " - 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 " - 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,24 +273,25 @@ 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) \ $(GO) clean -i ./...
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
integrations*.test \ integrations*.test \
e2e*.test \ e2e*.test \
tests/integration/gitea-integration-* \ tests/integration/gitea-integration-pgsql/ tests/integration/gitea-integration-mysql/ tests/integration/gitea-integration-mysql8/ tests/integration/gitea-integration-sqlite/ \
tests/integration/indexers-* \ tests/integration/gitea-integration-mssql/ tests/integration/indexers-mysql/ tests/integration/indexers-mysql8/ tests/integration/indexers-pgsql tests/integration/indexers-sqlite \
tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \ tests/integration/indexers-mssql tests/mysql.ini tests/mysql8.ini tests/pgsql.ini tests/mssql.ini man/ \
tests/e2e/gitea-e2e-*/ \ tests/e2e/gitea-e2e-pgsql/ tests/e2e/gitea-e2e-mysql/ tests/e2e/gitea-e2e-mysql8/ tests/e2e/gitea-e2e-sqlite/ \
tests/e2e/indexers-*/ \ tests/e2e/gitea-e2e-mssql/ tests/e2e/indexers-mysql/ tests/e2e/indexers-mysql8/ tests/e2e/indexers-pgsql/ tests/e2e/indexers-sqlite/ \
tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ tests/e2e/indexers-mssql/ 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
@# whitespace before it @# whitespace before it
@ -245,22 +305,13 @@ 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; \ exit 1; \
fi fi
.PHONY: fix .PHONY: misspell-check
fix: ## apply automated fixes to Go code misspell-check:
$(GO) run $(GOPLS_MODERNIZE_PACKAGE) -fix ./... go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS)
.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; \
fi
.PHONY: $(TAGS_EVIDENCE) .PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE): $(TAGS_EVIDENCE):
@ -272,95 +323,85 @@ 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 misspell-check swagger-validate security-check
.PHONY: lint .PHONY: lint
lint: lint-frontend lint-backend lint-spell ## lint everything lint: lint-frontend lint-backend
.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
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css ## lint frontend files lint-frontend: lint-js lint-css lint-md lint-swagger
.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 lint-md lint-swagger
.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-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,vue web_src/js build *.config.js tests/e2e
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,vue web_src/js build *.config.js tests/e2e --fix
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 web_src/css web_src/js/components/*.vue
.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 web_src/css web_src/js/components/*.vue --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 docs *.md
.PHONY: lint-spell
lint-spell: ## lint spelling
@go run $(MISSPELL_PACKAGE) -dict tools/misspellings.csv -error $(SPELLCHECK_FILES)
.PHONY: lint-spell-fix
lint-spell-fix: ## lint spelling and fix issues
@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 +411,43 @@ 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 $(GO_PACKAGES)
.PHONY: lint-go-gopls
lint-go-gopls: ## lint go files with gopls
@echo "Running gopls check..."
@GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES)
.PHONY: lint-editorconfig .PHONY: lint-editorconfig
lint-editorconfig: lint-editorconfig:
@echo "Running editorconfig check..." $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .github/workflows
@$(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
lint-templates: .venv node_modules ## lint template files
@node tools/lint-templates-svg.js
@poetry run djlint $(shell find templates -type f -iname '*.tmpl')
.PHONY: lint-yaml
lint-yaml: .venv ## lint yaml files
@poetry run yamllint -s .
.PHONY: watch .PHONY: watch
watch: ## watch everything and continuously rebuild watch:
@bash tools/watch.sh @bash build/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 +456,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,7 +479,7 @@ 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)
@ -467,17 +493,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)
@ -519,13 +543,33 @@ test-mysql\#%: integrations.mysql.test generate-ini-mysql
.PHONY: test-mysql-migration .PHONY: test-mysql-migration
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test
generate-ini-mysql8:
sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \
-e 's|{{TEST_MYSQL8_DBNAME}}|${TEST_MYSQL8_DBNAME}|g' \
-e 's|{{TEST_MYSQL8_USERNAME}}|${TEST_MYSQL8_USERNAME}|g' \
-e 's|{{TEST_MYSQL8_PASSWORD}}|${TEST_MYSQL8_PASSWORD}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
tests/mysql8.ini.tmpl > tests/mysql8.ini
.PHONY: test-mysql8
test-mysql8: integrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./integrations.mysql8.test
.PHONY: test-mysql8\#%
test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*)
.PHONY: test-mysql8-migration
test-mysql8-migration: migrations.mysql8.test migrations.individual.mysql8.test
generate-ini-pgsql: generate-ini-pgsql:
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \ sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
-e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \ -e 's|{{TEST_PGSQL_DBNAME}}|${TEST_PGSQL_DBNAME}|g' \
-e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \ -e 's|{{TEST_PGSQL_USERNAME}}|${TEST_PGSQL_USERNAME}|g' \
-e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \ -e 's|{{TEST_PGSQL_PASSWORD}}|${TEST_PGSQL_PASSWORD}|g' \
-e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \ -e 's|{{TEST_PGSQL_SCHEMA}}|${TEST_PGSQL_SCHEMA}|g' \
-e 's|{{TEST_MINIO_ENDPOINT}}|${TEST_MINIO_ENDPOINT}|g' \
-e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \
-e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \
-e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \
@ -564,7 +608,8 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
.PHONY: playwright .PHONY: playwright
playwright: deps-frontend playwright: $(PLAYWRIGHT_DIR)
npm install --no-save @playwright/test
npx playwright install $(PLAYWRIGHT_FLAGS) npx playwright install $(PLAYWRIGHT_FLAGS)
.PHONY: test-e2e% .PHONY: test-e2e%
@ -591,6 +636,14 @@ test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql
test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e/$*
.PHONY: test-e2e-mysql8
test-e2e-mysql8: playwright e2e.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./e2e.mysql8.test
.PHONY: test-e2e-mysql8\#%
test-e2e-mysql8\#%: playwright e2e.mysql8.test generate-ini-mysql8
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./e2e.mysql8.test -test.run TestE2e/$*
.PHONY: test-e2e-pgsql .PHONY: test-e2e-pgsql
test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test
@ -634,6 +687,9 @@ integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sq
integrations.mysql.test: git-check $(GO_SOURCES) integrations.mysql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql.test
integrations.mysql8.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mysql8.test
integrations.pgsql.test: git-check $(GO_SOURCES) integrations.pgsql.test: git-check $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test
@ -654,6 +710,11 @@ migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./migrations.mysql.test
.PHONY: migrations.mysql8.test
migrations.mysql8.test: $(GO_SOURCES) generate-ini-mysql8
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mysql8.test
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini ./migrations.mysql8.test
.PHONY: migrations.pgsql.test .PHONY: migrations.pgsql.test
migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test
@ -671,23 +732,36 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
.PHONY: migrations.individual.mysql.test .PHONY: migrations.individual.mysql.test
migrations.individual.mysql.test: $(GO_SOURCES) migrations.individual.mysql.test: $(GO_SOURCES)
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
done
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.mysql8.test
migrations.individual.mysql8.test: $(GO_SOURCES)
for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql8.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
done
.PHONY: migrations.individual.mysql8.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.pgsql.test .PHONY: migrations.individual.pgsql.test
migrations.individual.pgsql.test: $(GO_SOURCES) migrations.individual.pgsql.test: $(GO_SOURCES)
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
done
.PHONY: migrations.individual.pgsql.test\#% .PHONY: migrations.individual.pgsql.test\#%
migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$*
.PHONY: migrations.individual.mssql.test .PHONY: migrations.individual.mssql.test
migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg -test.failfast; \
done
.PHONY: migrations.individual.mssql.test\#% .PHONY: migrations.individual.mssql.test\#%
migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
@ -695,7 +769,9 @@ migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql
.PHONY: migrations.individual.sqlite.test .PHONY: migrations.individual.sqlite.test
migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \
GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \
done
.PHONY: migrations.individual.sqlite.test\#% .PHONY: migrations.individual.sqlite.test\#%
migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
@ -704,6 +780,9 @@ migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite
e2e.mysql.test: $(GO_SOURCES) e2e.mysql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql.test
e2e.mysql8.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mysql8.test
e2e.pgsql.test: $(GO_SOURCES) e2e.pgsql.test: $(GO_SOURCES)
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test
@ -721,17 +800,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
@ -739,39 +818,51 @@ generate-backend: $(TAGS_PREREQ) generate-go
.PHONY: generate-go .PHONY: generate-go
generate-go: $(TAGS_PREREQ) generate-go: $(TAGS_PREREQ)
@echo "Running go generate..." @echo "Running go generate..."
@CC= GOOS= GOARCH= CGO_ENABLED=0 $(GO) generate -tags '$(TAGS)' ./... @CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' $(GO_PACKAGES)
.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 $@
.PHONY: release .PHONY: release
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-check release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-docs release-check
$(DIST_DIRS): $(DIST_DIRS):
mkdir -p $(DIST_DIRS) mkdir -p $(DIST_DIRS)
.PHONY: release-windows .PHONY: release-windows
release-windows: | $(DIST_DIRS) release-windows: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
ifeq (,$(findstring gogit,$(TAGS))) ifeq (,$(findstring gogit,$(TAGS)))
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit .
endif
ifneq ($(DRONE_TAG),)
cp /build/* $(DIST)/binaries
endif endif
.PHONY: release-linux .PHONY: release-linux
release-linux: | $(DIST_DIRS) release-linux: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out gitea-$(VERSION) .
ifneq ($(DRONE_TAG),)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-darwin .PHONY: release-darwin
release-darwin: | $(DIST_DIRS) release-darwin: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin-10.12/amd64,darwin-10.12/arm64' -out gitea-$(VERSION) .
ifneq ($(DRONE_TAG),)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-freebsd .PHONY: release-freebsd
release-freebsd: | $(DIST_DIRS) release-freebsd: | $(DIST_DIRS)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w $(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) . CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'freebsd/amd64' -out gitea-$(VERSION) .
ifneq ($(DRONE_TAG),)
cp /build/* $(DIST)/binaries
endif
.PHONY: release-copy .PHONY: release-copy
release-copy: | $(DIST_DIRS) release-copy: | $(DIST_DIRS)
@ -783,7 +874,7 @@ release-check: | $(DIST_DIRS)
.PHONY: release-compress .PHONY: release-compress
release-compress: | $(DIST_DIRS) release-compress: | $(DIST_DIRS)
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PACKAGE) -k -9 $${file}; done; cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done;
.PHONY: release-sources .PHONY: release-sources
release-sources: | $(DIST_DIRS) release-sources: | $(DIST_DIRS)
@ -795,77 +886,74 @@ release-sources: | $(DIST_DIRS)
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz . tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
rm -f $(STORED_VERSION_FILE) rm -f $(STORED_VERSION_FILE)
.PHONY: deps .PHONY: release-docs
deps: deps-frontend deps-backend deps-tools deps-py ## install dependencies release-docs: | $(DIST_DIRS) docs
tar -czf $(DIST)/release/gitea-docs-$(VERSION).tar.gz -C ./docs .
.PHONY: deps-py .PHONY: docs
deps-py: .venv ## install python dependencies docs:
cd docs; bash scripts/trans-copy.sh;
.PHONY: deps
deps: deps-frontend deps-backend deps-tools
.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)
$(GO) install $(GOLANGCI_LINT_PACKAGE) & \ $(GO) install $(GOLANGCI_LINT_PACKAGE)
$(GO) install $(GXZ_PACKAGE) & \ $(GO) install $(GXZ_PAGAGE)
$(GO) install $(MISSPELL_PACKAGE) & \ $(GO) install $(MISSPELL_PACKAGE)
$(GO) install $(SWAGGER_PACKAGE) & \ $(GO) install $(SWAGGER_PACKAGE)
$(GO) install $(XGO_PACKAGE) & \ $(GO) install $(XGO_PACKAGE)
$(GO) install $(GO_LICENSES_PACKAGE) & \ $(GO) install $(GO_LICENSES_PACKAGE)
$(GO) install $(GOVULNCHECK_PACKAGE) & \ $(GO) install $(GOVULNCHECK_PACKAGE)
$(GO) install $(ACTIONLINT_PACKAGE) & \ $(GO) install $(ACTIONLINT_PACKAGE)
$(GO) install $(GOPLS_PACKAGE) & \
$(GO) install $(GOPLS_MODERNIZE_PACKAGE) & \
wait
node_modules: package-lock.json node_modules: package-lock.json
npm install --no-save npm install --no-save
@touch node_modules @touch node_modules
.venv: poetry.lock .PHONY: npm-update
poetry install npm-update: node-check | node_modules
@touch .venv npx updates -cu
.PHONY: update
update: update-js update-py ## update js and py dependencies
.PHONY: update-js
update-js: node-check | node_modules ## update js dependencies
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
npx nolyfill install
npm install --package-lock
@touch node_modules @touch node_modules
.PHONY: update-py .PHONY: fomantic
update-py: node-check | node_modules ## update py dependencies fomantic:
npx updates -u -f pyproject.toml rm -rf $(FOMANTIC_WORK_DIR)/build
rm -rf .venv poetry.lock cd $(FOMANTIC_WORK_DIR) && npm install --no-save
poetry install cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
@touch .venv cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
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
@rm -rf $(WEBPACK_DEST_ENTRIES) rm -rf $(WEBPACK_DEST_ENTRIES)
@echo "Running webpack..." npx webpack
@BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack
@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 build/generate-svg.js
.PHONY: svg-check .PHONY: svg-check
svg-check: svg svg-check: svg
@ -873,7 +961,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 +972,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,17 +986,21 @@ 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
generate-images: | node_modules generate-images: | node_modules
npm install --no-save fabric@6 imagemin-zopfli@7 npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7
node tools/generate-images.js $(TAGS) node build/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
@ -922,8 +1014,3 @@ docker:
# This endif closes the if at the top of the file # This endif closes the if at the top of the file
endif endif
# Disable parallel execution because it would break some targets that don't
# specify exact dependencies like 'backend' which does currently not depend
# on 'frontend' to enable Node.js-less builds from source tarballs.
.NOTPARALLEL:

201
README.md
View file

@ -1,17 +1,58 @@
# Gitea <p align="center">
<a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
</a>
</p>
<h1 align="center">Gitea - Git with a cup of tea</h1>
[![](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") <p align="center">
[![](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") <a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
[![](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") <img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
[![](https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg)](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") </a>
[![](https://img.shields.io/github/release/go-gitea/gitea.svg)](https://github.com/go-gitea/gitea/releases/latest "GitHub release") <a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
[![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") <img src="https://img.shields.io/discord/322538954119184384.svg">
[![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") </a>
[![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") <a href="https://app.codecov.io/gh/go-gitea/gitea" title="Codecov">
[![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) <img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
[![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") </a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a>
<a href="https://pkg.go.dev/code.gitea.io/gitea" title="GoDoc">
<img src="https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg">
</a>
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
</a>
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
</a>
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
</a>
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
</a>
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
<img
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
alt="Contribute with Gitpod"
/>
</a>
<a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg">
</a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a>
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p>
[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) <p align="center">
<a href="README_ZH.md">View this document in Chinese</a>
</p>
## Purpose ## Purpose
@ -21,24 +62,11 @@ painless way of setting up a self-hosted Git service.
As Gitea is written in Go, it works across **all** the platforms and As Gitea is written in Go, it works across **all** the platforms and
architectures that are supported by Go, including Linux, macOS, and architectures that are supported by Go, including Linux, macOS, and
Windows on x86, amd64, ARM and PowerPC architectures. Windows on x86, amd64, ARM and PowerPC architectures.
You can try it out using [the online demo](https://try.gitea.io/).
This project has been This project has been
[forked](https://blog.gitea.com/welcome-to-gitea/) from [forked](https://blog.gitea.io/2016/12/welcome-to-gitea/) from
[Gogs](https://gogs.io) since November of 2016, but a lot has changed. [Gogs](https://gogs.io) since November of 2016, but a lot has changed.
For online demonstrations, you can visit [demo.gitea.com](https://demo.gitea.com).
For accessing free Gitea service (with a limited number of repositories), you can visit [gitea.com](https://gitea.com/user/login).
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:
@ -51,52 +79,51 @@ or if SQLite support is required:
The `build` target is split into two sub-targets: The `build` target is split into two sub-targets:
- `make backend` which requires [Go Stable](https://go.dev/dl/), the required version is defined in [go.mod](/go.mod). - `make backend` which requires [Go Stable](https://go.dev/dl/), required version is defined in [go.mod](/go.mod).
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater. - `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
Internet connectivity is required to download the go and npm modules. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js. When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
More info: https://docs.gitea.com/installation/install-from-source Parallelism (`make -j <num>`) is not supported.
More info: https://docs.gitea.io/en-us/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: If you're interested in using our APIs, we have experimental
> If you're interested in using our APIs, we have experimental support with [documentation](https://docs.gitea.com/api). support with [documentation](https://try.gitea.io/api/swagger).
## Contributing ## Contributing
Expected workflow is: Fork -> Patch -> Push -> Pull Request Expected workflow is: Fork -> Patch -> Push -> Pull Request
> [!NOTE] NOTES:
>
> 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.** 1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
> 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks! 2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
## 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.io/en-us/contributing/translation-guidelines/
## 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.io/en-us/).
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://discourse.gitea.io/).
## 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 Hugo-based documentation theme is hosted at [gitea/theme](https://gitea.com/gitea/theme).
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/). The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
## Authors ## Authors
@ -124,6 +151,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<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/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/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> <a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
<a href="https://cynkra.com/" target="_blank"><img src="https://images.opencollective.com/cynkra/logo/square/64/192.png"></a>
## FAQ ## FAQ
@ -135,79 +163,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>

98
README_ZH.md Normal file
View file

@ -0,0 +1,98 @@
<p align="center">
<a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
</a>
</p>
<h1 align="center">Gitea - Git with a cup of tea</h1>
<p align="center">
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
</a>
<a href="https://discord.gg/Gitea" title="Join the Discord chat at https://discord.gg/Gitea">
<img src="https://img.shields.io/discord/322538954119184384.svg">
</a>
<a href="https://app.codecov.io/gh/go-gitea/gitea" title="Codecov">
<img src="https://codecov.io/gh/go-gitea/gitea/branch/main/graph/badge.svg">
</a>
<a href="https://goreportcard.com/report/code.gitea.io/gitea" title="Go Report Card">
<img src="https://goreportcard.com/badge/code.gitea.io/gitea">
</a>
<a href="https://pkg.go.dev/code.gitea.io/gitea" title="GoDoc">
<img src="https://pkg.go.dev/badge/code.gitea.io/gitea?status.svg">
</a>
<a href="https://github.com/go-gitea/gitea/releases/latest" title="GitHub release">
<img src="https://img.shields.io/github/release/go-gitea/gitea.svg">
</a>
<a href="https://www.codetriage.com/go-gitea/gitea" title="Help Contribute to Open Source">
<img src="https://www.codetriage.com/go-gitea/gitea/badges/users.svg">
</a>
<a href="https://opencollective.com/gitea" title="Become a backer/sponsor of gitea">
<img src="https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen">
</a>
<a href="https://opensource.org/licenses/MIT" title="License: MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg">
</a>
<a href="https://gitpod.io/#https://github.com/go-gitea/gitea">
<img
src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod"
alt="Contribute with Gitpod"
/>
</a>
<a href="https://crowdin.com/project/gitea" title="Crowdin">
<img src="https://badges.crowdin.net/gitea/localized.svg">
</a>
<a href="https://www.tickgit.com/browse?repo=github.com/go-gitea/gitea&branch=main" title="TODOs">
<img src="https://badgen.net/https/api.tickgit.com/badgen/github.com/go-gitea/gitea/main">
</a>
<a href="https://app.bountysource.com/teams/gitea" title="Bountysource">
<img src="https://img.shields.io/bountysource/team/gitea/activity">
</a>
</p>
<p align="center">
<a href="README.md">View this document in English</a>
</p>
## 目标
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86amd64还包括 ARM 和 PowerPC。
如果您想试用一下,请访问 [在线Demo](https://try.gitea.io/)
## 提示
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
3. 如果你要使用API请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
## 文档
关于如何安装请访问我们的 [文档站](https://docs.gitea.io/zh-cn/),如果没有找到对应的文档,你也可以通过 [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)|

View file

@ -4,22 +4,20 @@ The Gitea maintainers take security seriously.
If you discover a security issue, please bring it to their attention right away! If you discover a security issue, please bring it to their attention right away!
Previous vulnerabilities are listed at https://about.gitea.com/security.
## Reporting a Vulnerability ## Reporting a Vulnerability
Please **DO NOT** file a public issue, instead send your report privately to `security@gitea.io`. Please **DO NOT** file a public issue, instead send your report privately to `security@gitea.io`.
## Protecting Security Information ## Protecting Security Information
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body. Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body.
The PGP key is valid until July 9, 2025. The PGP key is valid until June 24, 2024.
``` ```
Key ID: 6FCD2D5B Key ID: 6FCD2D5B
Key Type: RSA Key Type: RSA
Expires: 7/9/2025 Expires: 6/24/2024
Key Size: 4096/4096 Key Size: 4096/4096
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
``` ```
@ -40,20 +38,20 @@ q+pHZl24JYR0Kf3T/ZiOC0cGd2QJqpJtg5J6S/OqfX9NH6MsCczO8pUC1N/aHH2X
CTme2nF56izORqDWKoiICteL3GpYsCV9nyCidcCmoQsS+DKvE86YhIhVIVWGRY2F CTme2nF56izORqDWKoiICteL3GpYsCV9nyCidcCmoQsS+DKvE86YhIhVIVWGRY2F
lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e lzpAjnN9/KLtQroutrm+Ft0mdjDiJUeFVl1cOHDhoyfCsQh62HumoyZoZvqzQd6e
AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB AbN11nq6aViMe2Q3je1AbiBnRnQSHxt1Tc8X4IshO3MQK1Sk7oPI6LA5oQARAQAB
tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBAhsD tCJHaXRlYSBTZWN1cml0eSA8c2VjdXJpdHlAZ2l0ZWEuaW8+iQJXBBMBCABBFiEE
BQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAFiEEPeA9HhRKfwaTWZncqv0jgW/N PeA9HhRKfwaTWZncqv0jgW/NLVsFAmK1Z/4CGwMFCQPCZwAFCwkIBwICIgIGFQoJ
LVsFAmaMse0FCQW4fW8ACgkQqv0jgW/NLVtXLg/+PF4G9Jhlui15BTNlEBJAV2P/ CAsCBBYCAwECHgcCF4AACgkQqv0jgW/NLVvnyxAAhxyNnWzw/rQO2qhzqicmZM94
1QlAV2krk0fP7tykn0FR9RfGIfVV/kwC1f+ouosYPQDDevl9LWdUIM+g94DtNo2o njSbOg+U2qMBvCdaqCQQeC+uaMmMzkDPanUUmLcyCkWqfCjPNjeSXAkE9npepVJI
7ACpcL3morvt5lVGpIZHL8TbX0qmFRXL/pB/cB+K6IwYvh2mrbp2zH+r4SCRyFYq 4HtmgxZQ94OU/h3CLbft+9GVRzUkVI29TSYGdvNtV2/BkNGoFFnKWQr119um0o6A
BjgXYFTI1MylJ1ShAjU6Z+m3oJ+2xs5LzHS0X6zkTjzA2Zl4zQzciQ9T+wJcE7Zi bgha2Uy5uY8o3ZIoiKkiHRaEoWIjjeBxJxYAojsZY4YElUmsQ3ik2joG6rhFesTa
HXdM1+YMF8KGNP8J9Rpug5oNDJ98lgZirRY7c3A/1xmYBiPnULwuuymdqEZO7l70 ofVt/bL8G2xzpOG26WGIxBbqf2qjV6OtZ0hu/vtTPHeIWMLq0Mz0V3PEDQWfkGPE
SeAlE1RWYX8kbOBnBb/KY4XwE3Vic1oEzc9DiPWVH1ElX86WNNsFzuyULiwoBoWg i2RYxxYDs2xzJhSQWqTNVLSq0m5xTJnbHhQPfdCX4C2jvFKgLdfmytQq49S7jiJb
pqZGhL9x1p5+46RGQSDczsHM7YGVtfYOiDo2PAVrmwsT0BnXnK8Oe3YIkvmUPEJu Z03HVOZ/PsyBlQfH9xJi06R5yQCMEA8h8Z5r3/NXW09kQ6OFRe6xshoTcxZGRPTo
OkLt0Z6A5n8pz8zhQzuApwBsK4ncJ8zTCpvz/pfKKqZC/Vnoh3gKGhDGvOZ+b5IJ srhwr3uPbmCRh+YEl7qBLU6+BC5k8IRTZXqhrj/aPJu3MxgbgwV8u3vLoFSXM2lb
0kUTe2JsbnwFixDUMDtacQ1op8XOyLoLVmgqLn0+Pws4XPBlMof2bioFir3yHKnP a61FgeCQ0O7lkgVswwF0RppCaH9Ul3ZDapet/vCRg4NVwm9zOI/8q/Vj0FKA1GDR
gNchsF1agrlSIo5GA8u4ga+IlCSfvFIKrl7+cxacKcJYt/vbOU5KcvVJI5EtHKCG JhRu8+Ce8zlFL65D34t+PprAzSeTlbv9um3x/ZIjCco7EEKSBylt+AZj/VyA6+e5
xfHjHY2ah1Qww7SxW6IXiRZZzPpsL2mBM2CD7N3qh9bV2s27wxYCdUodsIZbiyHe kjOQwRRc6dFJWBcorsSI2dG+H+QMF7ZabzmeCcz1v9HjLOPzYHoZAHhCmSppWTvX
oWPzfBnkmiAN8KlZxHm5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen AJy6+lhfW2OUTqQeYSi5Ag0EYrVn/gEQALrFLQjCR3GjuHSindz0rd3Fnx/t7Sen
T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN T+p07yCSSoSlmnJHCQmwh4vfg1blyz0zZ4vkIhtpHsEgc+ZAG+WQXSsJ2iRz+eSN
GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46 GwoOQl4XC3n+QWkc1ws+btr48+6UqXIQU+F8TPQyx/PIgi2nZXJB7f5+mjCqsk46
XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E XvH4nTr4kJjuqMSR/++wvre2qNQRa/q/dTsK0OaN/mJsdX6Oi+aGNaQJUhIG7F+E
@ -64,20 +62,20 @@ qoExANj5lUTZPe8M50lI182FrcjAN7dClO3QI6pg7wy0erMxfFly3j8UQ91ysS9T
s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/ s+GsP9I3cmWWQcKYxWHtE8xTXnNCVPFZQj2nwhJzae8ypfOtulBRA3dUKWGKuDH/
axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt axFENhUsT397aOU3qkP/od4a64JyNIEo4CTTSPVeWd7njsGqli2U3A4xL2CcyYvt
D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x D/MWcMBGEoLSNTswwKdom4FaJpn5KThnK/T0bQcmJblJhoCtppXisbexZnCpuS0x
Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYCGwwWIQQ94D0eFEp/BpNZmdyq/SOBb80t Zdlm2T14KJ3LABEBAAGJAjwEGAEIACYWIQQ94D0eFEp/BpNZmdyq/SOBb80tWwUC
WwUCZoyyjQUJBbh+DwAKCRCq/SOBb80tW18XD/9MXztmf01MT+1kZdBouZ/7Rp/7 YrVn/gIbDAUJA8JnAAAKCRCq/SOBb80tWyTBD/9AGpW6QoDF7zYjHAozH9S5RGCA
9kuqo//B1G+RXau4oFtPqb67kNe2WaIc3u5B73PUHsMf3i6z4ib2KbMhZZerLn0O Y7E82dG/0xmFUwPprAG0BKmmgU6TiipyVGmKIXGYYYU92pMnbvXkYQMoa+WJNncN
dRglcuPeNWmsASY3dH/XVG0cT0zvvWegagd12TJEl3Vs+7XNrOw4cwDj9L1+GH9m D3fY52UeXeffTf4cFpStlzi9xgYtOLhFamzYu/4xhkjOX+xhOSXscCiFRyT8cF3B
kSt4uaANWn/6a3RvMRhiVEYuNwhAzcKaactPmYqrLJgoVLbRSDkgyHaMQ2jKgLxk O6c5BHU+Zj0/rGPgOyPUbx7l7B9MubB/41nNX35k08e+8T3wtWDb4XF+15HnRfva
ifS/fvluGV0ub2Po6DJiqfRpd1tDvPhe9y1+r1WFDZsOcvTcZUfSt/7dXMGfqGu0 6fblO8wgU25Orv2Rm1jnKGa9DxJ8nE40IMrqDapENtDuL+zKJsvR0+ptWvEyL56U
2daVFlfeSXSALrDE5uc0UxodHCpP3sqRYDZevGLBRaaTkIjYXG/+N898+7K5WJF4 GtJJG5un6mXiLKuRQT0DEv4MdZRHDgDstDnqcbEiazVEbUuvhZZob6lRY2A19m1+
xXOLWxM2cwGkG7eC9pugcDnBp9XlF7O+GBiZ05JUe5flXDQFZ+h3exjopu6KHF1B 7zfnDxkhqCA1RCnv4fdvcPdCMMFHwLpdhjgW0aI/uwgwrvsEz5+JRlnLvdQHlPAg
RnzNy8LC0UKb+AuvRIOLV92a9Q9wGWU/jaVDu6nZ0umAeuSzxiHoDsonm0Fl9QAz q7l2fGcBSpz9U0ayyfRPjPntsNCtZl1UDxGLeciPkZhyG84zEWQbk/j52ZpRN+Ik
2/xCokebuoeLrEK7R2af3X86mqq3sVO4ax+HPYChzOaVQBiHUW/TAldWcldYYphR ALpRLa8RBFmFSmXDUmwQrmm1EmARyQXwweKU31hf8ZGbCp2lPuRYm1LuGiirXSVP
/e2WsbmQfvCRtz/bZfo+aUVnrHNjzVMtF2SszdVmA/04Y8pS28MqtuRqhm5DPOOd GysjRAJgW+VRpBKOzFQoUAUbReVWSaCwT8s17THzf71DdDb6CTj31jMLLYWwBpA/
g1YeUywK5jRZ1twyo1kzJEFPLaoeaXaycsR1PMVBW0Urik5mrR/pOWq7PPoZoKb2 i73DgobDZMIGEZZC1EKqza8eh11xfyHFzGec03tbh+lIen+5IiRtWiEWkDS9ll0G
lXYLE8bwkuQTmsyL1g== zgS/ZdziCvdAutqnGA==
=9i7d =gZWO
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
``` ```

2
assets/emoji.json generated

File diff suppressed because one or more lines are too long

422
assets/go-licenses.json generated

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

@ -12,7 +12,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -59,7 +58,7 @@ func main() {
// use old en-US as the base, and copy the new translations to the old locales // use old en-US as the base, and copy the new translations to the old locales
enUsOld := inisOld["options/locale/locale_en-US.ini"] enUsOld := inisOld["options/locale/locale_en-US.ini"]
brokenWarned := make(container.Set[string]) brokenWarned := map[string]bool{}
for path, iniOld := range inisOld { for path, iniOld := range inisOld {
if iniOld == enUsOld { if iniOld == enUsOld {
continue continue
@ -78,7 +77,7 @@ func main() {
broken := oldStr != "" && strings.Count(oldStr, "%") != strings.Count(newStr, "%") broken := oldStr != "" && strings.Count(oldStr, "%") != strings.Count(newStr, "%")
broken = broken || strings.Contains(oldStr, "\n") || strings.Contains(oldStr, "\n") broken = broken || strings.Contains(oldStr, "\n") || strings.Contains(oldStr, "\n")
if broken { if broken {
brokenWarned.Add(secOld.Name() + "." + keyEnUs.Name()) brokenWarned[secOld.Name()+"."+keyEnUs.Name()] = true
fmt.Println("----") fmt.Println("----")
fmt.Printf("WARNING: skip broken locale: %s , [%s] %s\n", path, secEnUS.Name(), keyEnUs.Name()) fmt.Printf("WARNING: skip broken locale: %s , [%s] %s\n", path, secEnUS.Name(), keyEnUs.Name())
fmt.Printf("\told: %s\n", strings.ReplaceAll(oldStr, "\n", "\\n")) fmt.Printf("\told: %s\n", strings.ReplaceAll(oldStr, "\n", "\\n"))
@ -104,7 +103,7 @@ func main() {
broken = broken || strings.HasPrefix(str, "`\"") broken = broken || strings.HasPrefix(str, "`\"")
broken = broken || strings.Count(str, `"`)%2 == 1 broken = broken || strings.Count(str, `"`)%2 == 1
broken = broken || strings.Count(str, "`")%2 == 1 broken = broken || strings.Count(str, "`")%2 == 1
if broken && !brokenWarned.Contains(sec.Name()+"."+key.Name()) { if broken && !brokenWarned[sec.Name()+"."+key.Name()] {
fmt.Printf("WARNING: found broken locale: %s , [%s] %s\n", path, sec.Name(), key.Name()) fmt.Printf("WARNING: found broken locale: %s , [%s] %s\n", path, sec.Name(), key.Name())
fmt.Printf("\tstr: %s\n", strings.ReplaceAll(str, "\n", "\\n")) fmt.Printf("\tstr: %s\n", strings.ReplaceAll(str, "\n", "\\n"))
fmt.Println("----") fmt.Println("----")

View file

@ -69,7 +69,6 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error)
co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`)) co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`))
co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`))
@ -204,6 +203,17 @@ Example:
`, "file-batch-exec") `, "file-batch-exec")
} }
func getGoVersion() string {
goModFile, err := os.ReadFile("go.mod")
if err != nil {
log.Fatalf(`Faild to read "go.mod": %v`, err)
os.Exit(1)
}
goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`)
goModVersionLine := goModVersionRegex.Find(goModFile)
return string(goModVersionLine[3:])
}
func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) { func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) {
fileFilter := mainOptions["file-filter"] fileFilter := mainOptions["file-filter"]
if fileFilter == "" { if fileFilter == "" {
@ -268,8 +278,7 @@ func main() {
log.Print("the -d option is not supported by gitea-fmt") log.Print("the -d option is not supported by gitea-fmt")
} }
cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w"))) cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w")))
cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...))) cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...)))
cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...)))
default: default:
log.Fatalf("unknown cmd: %s %v", subCmd, subArgs) log.Fatalf("unknown cmd: %s %v", subCmd, subArgs)
} }

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)
} }

View file

@ -25,7 +25,7 @@ import (
const ( const (
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
maxUnicodeVersion = 15 maxUnicodeVersion = 14
) )
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")
@ -53,6 +53,8 @@ func (e Emoji) MarshalJSON() ([]byte, error) {
} }
func main() { func main() {
var err error
flag.Parse() flag.Parse()
// generate data // generate data
@ -81,6 +83,8 @@ var replacer = strings.NewReplacer(
var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`)
func generate() ([]byte, error) { func generate() ([]byte, error) {
var err error
// load gemoji data // load gemoji data
res, err := http.Get(gemojiURL) res, err := http.Get(gemojiURL)
if err != nil { if err != nil {

View file

@ -15,8 +15,6 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"code.gitea.io/gitea/modules/container"
) )
// regexp is based on go-license, excluding README and NOTICE // regexp is based on go-license, excluding README and NOTICE
@ -57,14 +55,20 @@ func main() {
// yml // yml
// //
// It could be removed once we have a better regex. // It could be removed once we have a better regex.
excludedExt := container.SetOf(".gitignore", ".go", ".mod", ".sum", ".toml", ".yml") excludedExt := map[string]bool{
".gitignore": true,
".go": true,
".mod": true,
".sum": true,
".toml": true,
".yml": true,
}
var paths []string var paths []string
err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error { err := filepath.WalkDir(base, func(path string, entry fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
} }
if entry.IsDir() || !licenseRe.MatchString(entry.Name()) || excludedExt.Contains(filepath.Ext(entry.Name())) { if entry.IsDir() || !licenseRe.MatchString(entry.Name()) || excludedExt[filepath.Ext(entry.Name())] {
return nil return nil
} }
paths = append(paths, path) paths = append(paths, path)

82
build/generate-images.js Executable file
View file

@ -0,0 +1,82 @@
#!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo';
import {fabric} from 'fabric';
import {readFile, writeFile} from 'node:fs/promises';
function exit(err) {
if (err) console.error(err);
process.exit(err ? 1 : 0);
}
function loadSvg(svg) {
return new Promise((resolve) => {
fabric.loadSVGFromString(svg, (objects, options) => {
resolve({objects, options});
});
});
}
async function generate(svg, path, {size, bg}) {
const outputFile = new URL(path, import.meta.url);
if (String(outputFile).endsWith('.svg')) {
const {data} = optimize(svg, {
plugins: [
'preset-default',
'removeDimensions',
{
name: 'addAttributesToSVGElement',
params: {attributes: [{width: size}, {height: size}]}
},
],
});
await writeFile(outputFile, data);
return;
}
const {objects, options} = await loadSvg(svg);
const canvas = new fabric.Canvas();
canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) {
canvas.add(new fabric.Rect({
left: 0,
top: 0,
height: size * (1 / (size / options.height)),
width: size * (1 / (size / options.width)),
fill: 'white',
}));
}
canvas.add(fabric.util.groupSVGElements(objects, options));
canvas.renderAll();
let png = Buffer.from([]);
for await (const chunk of canvas.createPNGStream()) {
png = Buffer.concat([png, chunk]);
}
png = await imageminZopfli({more: true})(png);
await writeFile(outputFile, png);
}
async function main() {
const gitea = process.argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
await Promise.all([
generate(logoSvg, '../public/img/logo.svg', {size: 32}),
generate(logoSvg, '../public/img/logo.png', {size: 512}),
generate(faviconSvg, '../public/img/favicon.svg', {size: 32}),
generate(faviconSvg, '../public/img/favicon.png', {size: 180}),
generate(logoSvg, '../public/img/avatar_default.png', {size: 200}),
generate(logoSvg, '../public/img/apple-touch-icon.png', {size: 180, bg: true}),
gitea && generate(logoSvg, '../public/img/gitea.svg', {size: 32}),
]);
}
main().then(exit).catch(exit);

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

@ -0,0 +1,122 @@
//go:build ignore
package main
import (
"archive/tar"
"compress/gzip"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"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)
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
}
if strings.HasPrefix(filepath.Base(hdr.Name), "README") {
continue
}
if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") {
continue
}
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt")))
if err != nil {
log.Fatalf("Failed to create new file. %s", err)
}
defer out.Close()
if _, err := io.Copy(out, tr); err != nil {
log.Fatalf("Failed to write new file. %s", err)
} else {
fmt.Printf("Written %s\n", out.Name())
}
}
fmt.Println("Done")
}

66
build/generate-svg.js Executable file
View file

@ -0,0 +1,66 @@
#!/usr/bin/env node
import fastGlob from 'fast-glob';
import {optimize} from 'svgo';
import {parse} from 'node:path';
import {readFile, writeFile, mkdir} from 'node:fs/promises';
import {fileURLToPath} from 'node:url';
const glob = (pattern) => fastGlob.sync(pattern, {
cwd: fileURLToPath(new URL('..', import.meta.url)),
absolute: true,
});
function exit(err) {
if (err) console.error(err);
process.exit(err ? 1 : 0);
}
async function processFile(file, {prefix, fullName} = {}) {
let name;
if (fullName) {
name = fullName;
} else {
name = parse(file).name;
if (prefix) name = `${prefix}-${name}`;
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
}
// Set the `xmlns` attribute so that the files are displayable in standalone documents
// The svg backend module will strip the attribute during startup for inline display
const {data} = optimize(await readFile(file, 'utf8'), {
plugins: [
{name: 'preset-default'},
{name: 'removeDimensions'},
{name: 'prefixIds', params: {prefix: () => name}},
{name: 'addClassesToSVGElement', params: {classNames: ['svg', name]}},
{
name: 'addAttributesToSVGElement', params: {
attributes: [
{'xmlns': 'http://www.w3.org/2000/svg'},
{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'},
]
}
},
],
});
await writeFile(fileURLToPath(new URL(`../public/img/svg/${name}.svg`, import.meta.url)), data);
}
function processFiles(pattern, opts) {
return glob(pattern).map((file) => processFile(file, opts));
}
async function main() {
try {
await mkdir(fileURLToPath(new URL('../public/img/svg', import.meta.url)), {recursive: true});
} catch {}
await Promise.all([
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
...processFiles('web_src/svg/*.svg'),
...processFiles('public/img/gitea.svg', {fullName: 'gitea-gitea'}),
]);
}
main().then(exit).catch(exit);

View file

@ -4,42 +4,44 @@
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"
) )
var ( var (
// CmdActions represents the available actions sub-commands. // CmdActions represents the available actions sub-commands.
CmdActions = &cli.Command{ CmdActions = cli.Command{
Name: "actions", Name: "actions",
Usage: "Manage Gitea Actions", Usage: "",
Commands: []*cli.Command{ Description: "Commands for managing Gitea Actions",
Subcommands: []cli.Command{
subcmdActionsGenRunnerToken, subcmdActionsGenRunnerToken,
}, },
} }
subcmdActionsGenRunnerToken = &cli.Command{ subcmdActionsGenRunnerToken = cli.Command{
Name: "generate-runner-token", Name: "generate-runner-token",
Usage: "Generate a new token for a runner to use to register with the server", Usage: "Generate a new token for a runner to use to register with the server",
Action: runGenerateActionsRunnerToken, Action: runGenerateActionsRunnerToken,
Aliases: []string{"grt"}, Aliases: []string{"grt"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ cli.StringFlag{
Name: "scope", Name: "scope, s",
Aliases: []string{"s"}, Value: "",
Value: "", Usage: "{owner}[/{repo}] - leave empty for a global runner",
Usage: "{owner}[/{repo}] - leave empty for a global runner",
}, },
}, },
} }
) )
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")
@ -48,6 +50,6 @@ func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error {
if extra.HasError() { if extra.HasError() {
return handleCliResponseExtra(extra) return handleCliResponseExtra(extra)
} }
_, _ = fmt.Printf("%s\n", respText.Text) _, _ = fmt.Printf("%s\n", respText)
return nil return nil
} }

View file

@ -5,25 +5,36 @@
package cmd package cmd
import ( import (
"context" "errors"
"fmt" "fmt"
"net/url"
"os"
"strings"
"text/tabwriter"
asymkey_model "code.gitea.io/gitea/models/asymkey"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/graceful"
"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"
"code.gitea.io/gitea/modules/util"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/smtp"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli/v3" "github.com/urfave/cli"
) )
var ( var (
// CmdAdmin represents the available admin sub-command. // CmdAdmin represents the available admin sub-command.
CmdAdmin = &cli.Command{ CmdAdmin = cli.Command{
Name: "admin", Name: "admin",
Usage: "Perform common administrative operations", Usage: "Command line interface to perform common administrative operations",
Commands: []*cli.Command{ Subcommands: []cli.Command{
subcmdUser, subcmdUser,
subcmdRepoSyncReleases, subcmdRepoSyncReleases,
subcmdRegenerate, subcmdRegenerate,
@ -32,81 +43,314 @@ var (
}, },
} }
subcmdRepoSyncReleases = &cli.Command{ subcmdRepoSyncReleases = cli.Command{
Name: "repo-sync-releases", Name: "repo-sync-releases",
Usage: "Synchronize repository releases with tags", Usage: "Synchronize repository releases with tags",
Action: runRepoSyncReleases, Action: runRepoSyncReleases,
} }
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,
}, },
} }
subcmdAuth = &cli.Command{ microcmdRegenHooks = cli.Command{
Name: "hooks",
Usage: "Regenerate git-hooks",
Action: runRegenerateHooks,
}
microcmdRegenKeys = cli.Command{
Name: "keys",
Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys,
}
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(), cmdAuthAddLdapBindDn,
microcmdAuthUpdateLdapBindDn(), cmdAuthUpdateLdapBindDn,
microcmdAuthAddLdapSimpleAuth(), cmdAuthAddLdapSimpleAuth,
microcmdAuthUpdateLdapSimpleAuth(), cmdAuthUpdateLdapSimpleAuth,
microcmdAuthAddSMTP(), microcmdAuthAddSMTP,
microcmdAuthUpdateSMTP(), microcmdAuthUpdateSMTP,
microcmdAuthList, microcmdAuthList,
microcmdAuthDelete, microcmdAuthDelete,
}, },
} }
subcmdSendMail = &cli.Command{ microcmdAuthList = cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
cli.IntFlag{
Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table",
Value: 0,
},
cli.IntFlag{
Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8,
},
cli.IntFlag{
Name: "padding",
Usage: "padding added to a cell before computing its width",
Value: 1,
},
cli.StringFlag{
Name: "pad-char",
Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`,
Value: "\t",
},
cli.BoolFlag{
Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns",
},
},
}
idFlag = cli.Int64Flag{
Name: "id",
Usage: "ID of authentication source",
}
microcmdAuthDelete = cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag},
Action: runDeleteAuth,
}
oauthCLIFlags = []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
cli.StringFlag{
Name: "custom-tenant-id",
Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints",
},
cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
cli.StringFlag{
Name: "icon-url",
Value: "",
Usage: "Custom icon URL for OAuth2 login source",
},
cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
cli.StringFlag{
Name: "required-claim-value",
Value: "",
Usage: "Claim value that has to be set to allow users to login with this source",
},
cli.StringFlag{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
cli.StringFlag{
Name: "group-team-map",
Value: "",
Usage: "JSON mapping between groups and org teams",
},
cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups",
},
}
microcmdAuthUpdateOauth = cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
}
microcmdAuthAddOauth = cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
}
subcmdSendMail = cli.Command{
Name: "sendmail", Name: "sendmail",
Usage: "Send a message to all users", Usage: "Send a message to all users",
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",
Usage: "a content of a message", Usage: "a content of a message",
Value: "", Value: "",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "force", Name: "force,f",
Aliases: []string{"f"}, Usage: "A flag to bypass a confirmation step",
Usage: "A flag to bypass a confirmation step",
}, },
}, },
} }
smtpCLIFlags = []cli.Flag{
cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
cli.StringFlag{
Name: "auth-type",
Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
cli.StringFlag{
Name: "host",
Value: "",
Usage: "SMTP Host",
},
cli.IntFlag{
Name: "port",
Usage: "SMTP Port",
},
cli.BoolTFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
},
cli.BoolTFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
},
cli.StringFlag{
Name: "helo-hostname",
Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
},
cli.BoolTFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
},
cli.StringFlag{
Name: "allowed-domains",
Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
},
cli.BoolTFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
},
cli.BoolTFlag{
Name: "active",
Usage: "This Authentication Source is Activated.",
},
}
microcmdAuthAddSMTP = cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: runAddSMTP,
Flags: smtpCLIFlags,
}
microcmdAuthUpdateSMTP = cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: runUpdateSMTP,
Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...),
}
) )
func idFlag() *cli.Int64Flag { func runRepoSyncReleases(_ *cli.Context) error {
return &cli.Int64Flag{ ctx, cancel := installSignals()
Name: "id", defer cancel()
Usage: "ID of authentication source",
}
}
func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
} }
if err := git.InitSimple(ctx); err != nil {
return err
}
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,
@ -122,25 +366,25 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
log.Trace("Processing next %d repos of %d", len(repos), count) log.Trace("Processing next %d repos of %d", len(repos), count)
for _, repo := range repos { for _, repo := range repos {
log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath()) log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RepoPath())
gitRepo, err := gitrepo.OpenRepository(ctx, repo) gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil { if err != nil {
log.Warn("OpenRepository: %v", err) log.Warn("OpenRepository: %v", err)
continue continue
} }
oldnum, err := getReleaseCount(ctx, repo.ID) oldnum, err := getReleaseCount(repo.ID)
if err != nil { if err != nil {
log.Warn(" GetReleaseCountByRepoID: %v", err) log.Warn(" GetReleaseCountByRepoID: %v", err)
} }
log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum)
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil {
log.Warn(" SyncReleasesWithTags: %v", err) log.Warn(" SyncReleasesWithTags: %v", err)
gitRepo.Close() gitRepo.Close()
continue continue
} }
count, err = getReleaseCount(ctx, repo.ID) count, err = getReleaseCount(repo.ID)
if err != nil { if err != nil {
log.Warn(" GetReleaseCountByRepoID: %v", err) log.Warn(" GetReleaseCountByRepoID: %v", err)
gitRepo.Close() gitRepo.Close()
@ -156,12 +400,360 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error {
return nil return nil
} }
func getReleaseCount(ctx context.Context, id int64) (int64, error) { func getReleaseCount(id int64) (int64, error) {
return db.Count[repo_model.Release]( return repo_model.GetReleaseCountByRepoID(
ctx, db.DefaultContext,
id,
repo_model.FindReleasesOptions{ repo_model.FindReleasesOptions{
RepoID: id,
IncludeTags: true, IncludeTags: true,
}, },
) )
} }
func runRegenerateHooks(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
func runRegenerateKeys(_ *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
return asymkey_model.RewriteAllPublicKeys()
}
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: c.String("custom-token-url"),
AuthURL: c.String("custom-auth-url"),
ProfileURL: c.String("custom-profile-url"),
EmailURL: c.String("custom-email-url"),
Tenant: c.String("custom-tenant-id"),
}
} else {
customURLMapping = nil
}
return &oauth2.Source{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"),
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
GroupClaimName: c.String("group-claim-name"),
AdminGroup: c.String("admin-group"),
RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
}
}
func runAddOauth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
return auth_model.CreateSource(&auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
Cfg: config,
})
}
func runUpdateOauth(c *cli.Context) error {
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
return err
}
oAuth2Config := source.Cfg.(*oauth2.Source)
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("provider") {
oAuth2Config.Provider = c.String("provider")
}
if c.IsSet("key") {
oAuth2Config.ClientID = c.String("key")
}
if c.IsSet("secret") {
oAuth2Config.ClientSecret = c.String("secret")
}
if c.IsSet("auto-discover-url") {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
if c.IsSet("icon-url") {
oAuth2Config.IconURL = c.String("icon-url")
}
if c.IsSet("scopes") {
oAuth2Config.Scopes = c.StringSlice("scopes")
}
if c.IsSet("required-claim-name") {
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
}
if c.IsSet("required-claim-value") {
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
}
if c.IsSet("group-claim-name") {
oAuth2Config.GroupClaimName = c.String("group-claim-name")
}
if c.IsSet("admin-group") {
oAuth2Config.AdminGroup = c.String("admin-group")
}
if c.IsSet("restricted-group") {
oAuth2Config.RestrictedGroup = c.String("restricted-group")
}
if c.IsSet("group-team-map") {
oAuth2Config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
// update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{}
if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
customURLMapping.TokenURL = c.String("custom-token-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
customURLMapping.AuthURL = c.String("custom-auth-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
customURLMapping.ProfileURL = c.String("custom-profile-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
customURLMapping.EmailURL = c.String("custom-email-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") {
customURLMapping.Tenant = c.String("custom-tenant-id")
}
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
return auth_model.UpdateSource(source)
}
func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
}
conf.Auth = c.String("auth-type")
}
if c.IsSet("host") {
conf.Host = c.String("host")
}
if c.IsSet("port") {
conf.Port = c.Int("port")
}
if c.IsSet("allowed-domains") {
conf.AllowedDomains = c.String("allowed-domains")
}
if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.BoolT("force-smtps")
}
if c.IsSet("skip-verify") {
conf.SkipVerify = c.BoolT("skip-verify")
}
if c.IsSet("helo-hostname") {
conf.HeloHostname = c.String("helo-hostname")
}
if c.IsSet("disable-helo") {
conf.DisableHelo = c.BoolT("disable-helo")
}
if c.IsSet("skip-local-2fa") {
conf.SkipLocalTwoFA = c.BoolT("skip-local-2fa")
}
return nil
}
func runAddSMTP(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
if !c.IsSet("name") || len(c.String("name")) == 0 {
return errors.New("name must be set")
}
if !c.IsSet("host") || len(c.String("host")) == 0 {
return errors.New("host must be set")
}
if !c.IsSet("port") {
return errors.New("port must be set")
}
active := true
if c.IsSet("active") {
active = c.BoolT("active")
}
var smtpConfig smtp.Source
if err := parseSMTPConfig(c, &smtpConfig); err != nil {
return err
}
// If not set default to PLAIN
if len(smtpConfig.Auth) == 0 {
smtpConfig.Auth = "PLAIN"
}
return auth_model.CreateSource(&auth_model.Source{
Type: auth_model.SMTP,
Name: c.String("name"),
IsActive: active,
Cfg: &smtpConfig,
})
}
func runUpdateSMTP(c *cli.Context) error {
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
return err
}
smtpConfig := source.Cfg.(*smtp.Source)
if err := parseSMTPConfig(c, smtpConfig); err != nil {
return err
}
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("active") {
source.IsActive = c.BoolT("active")
}
source.Cfg = smtpConfig
return auth_model.UpdateSource(source)
}
func runListAuth(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
authSources, err := auth_model.Sources()
if err != nil {
return err
}
flags := tabwriter.AlignRight
if c.Bool("vertical-bars") {
flags |= tabwriter.Debug
}
padChar := byte('\t')
if len(c.String("pad-char")) > 0 {
padChar = c.String("pad-char")[0]
}
// loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
}
w.Flush()
return nil
}
func runDeleteAuth(c *cli.Context) error {
if !c.IsSet("id") {
return fmt.Errorf("--id flag is missing")
}
ctx, cancel := installSignals()
defer cancel()
if err := initDB(ctx); err != nil {
return err
}
source, err := auth_model.GetSourceByID(c.Int64("id"))
if err != nil {
return err
}
return auth_service.DeleteSource(source)
}

View file

@ -1,106 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"text/tabwriter"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
auth_service "code.gitea.io/gitea/services/auth"
"github.com/urfave/cli/v3"
)
var (
microcmdAuthDelete = &cli.Command{
Name: "delete",
Usage: "Delete specific auth source",
Flags: []cli.Flag{idFlag()},
Action: runDeleteAuth,
}
microcmdAuthList = &cli.Command{
Name: "list",
Usage: "List auth sources",
Action: runListAuth,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "min-width",
Usage: "Minimal cell width including any padding for the formatted table",
Value: 0,
},
&cli.IntFlag{
Name: "tab-width",
Usage: "width of tab characters in formatted table (equivalent number of spaces)",
Value: 8,
},
&cli.IntFlag{
Name: "padding",
Usage: "padding added to a cell before computing its width",
Value: 1,
},
&cli.StringFlag{
Name: "pad-char",
Usage: `ASCII char used for padding if padchar == '\\t', the Writer will assume that the width of a '\\t' in the formatted output is tabwidth, and cells are left-aligned independent of align_left (for correct-looking results, tabwidth must correspond to the tab width in the viewer displaying the result)`,
Value: "\t",
},
&cli.BoolFlag{
Name: "vertical-bars",
Usage: "Set to true to print vertical bars between columns",
},
},
}
)
func runListAuth(ctx context.Context, c *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
authSources, err := db.Find[auth_model.Source](ctx, auth_model.FindSourcesOptions{})
if err != nil {
return err
}
flags := tabwriter.AlignRight
if c.Bool("vertical-bars") {
flags |= tabwriter.Debug
}
padChar := byte('\t')
if len(c.String("pad-char")) > 0 {
padChar = c.String("pad-char")[0]
}
// loop through each source and print
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range authSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, source.Type.String(), source.IsActive)
}
w.Flush()
return nil
}
func runDeleteAuth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := initDB(ctx); err != nil {
return err
}
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
return auth_service.DeleteSource(ctx, source)
}

View file

@ -9,209 +9,168 @@ 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"
) )
type ( type (
authService struct { authService struct {
initDB func(ctx context.Context) error initDB func(ctx context.Context) error
createAuthSource func(context.Context, *auth.Source) error createAuthSource func(*auth.Source) error
updateAuthSource func(context.Context, *auth.Source) error updateAuthSource func(*auth.Source) error
getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error) getAuthSourceByID func(id int64) (*auth.Source, error)
} }
) )
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.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "not-active", Name: "not-active",
Usage: "Deactivate the authentication source.", Usage: "Deactivate the authentication source.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "active", Name: "active",
Usage: "Activate the authentication source.", Usage: "Activate the authentication source.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "security-protocol", Name: "security-protocol",
Usage: "Security protocol name.", Usage: "Security protocol name.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-tls-verify", Name: "skip-tls-verify",
Usage: "Disable TLS verification.", Usage: "Disable TLS verification.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "host", Name: "host",
Usage: "The address where the LDAP server can be reached.", Usage: "The address where the LDAP server can be reached.",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "port", Name: "port",
Usage: "The port to use when connecting to the LDAP server.", Usage: "The port to use when connecting to the LDAP server.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "user-search-base", Name: "user-search-base",
Usage: "The LDAP base at which user accounts will be searched for.", Usage: "The LDAP base at which user accounts will be searched for.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "user-filter", Name: "user-filter",
Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "admin-filter", Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.", Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "restricted-filter", Name: "restricted-filter",
Usage: "An LDAP filter specifying if a user should be given restricted status.", Usage: "An LDAP filter specifying if a user should be given restricted status.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "allow-deactivate-all", Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.", Usage: "Allow empty search results to deactivate all users.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "username-attribute", Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.", Usage: "The attribute of the users LDAP record containing the user name.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "firstname-attribute", Name: "firstname-attribute",
Usage: "The attribute of the users LDAP record containing the users first name.", Usage: "The attribute of the users LDAP record containing the users first name.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "surname-attribute", Name: "surname-attribute",
Usage: "The attribute of the users LDAP record containing the users surname.", Usage: "The attribute of the users LDAP record containing the users surname.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "email-attribute", Name: "email-attribute",
Usage: "The attribute of the users LDAP record containing the users email address.", Usage: "The attribute of the users LDAP record containing the users email address.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "public-ssh-key-attribute", Name: "public-ssh-key-attribute",
Usage: "The attribute of the users LDAP record containing the users public ssh key.", Usage: "The attribute of the users LDAP record containing the users public ssh key.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-local-2fa", Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source", Usage: "Set to true to skip local 2fa for users authenticated by this source",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "avatar-attribute", Name: "avatar-attribute",
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.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "bind-password", Name: "bind-password",
Usage: "The password for the Bind DN, if any.", Usage: "The password for the Bind DN, if any.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "attributes-in-bind", Name: "attributes-in-bind",
Usage: "Fetch attributes in bind DN context.", Usage: "Fetch attributes in bind DN context.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "synchronize-users", Name: "synchronize-users",
Usage: "Enable user synchronization.", Usage: "Enable user synchronization.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "disable-synchronize-users", Name: "disable-synchronize-users",
Usage: "Disable user synchronization.", Usage: "Disable user synchronization.",
}, },
&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 users DN.",
}) })
}
func microcmdAuthAddLdapBindDn() *cli.Command { cmdAuthAddLdapBindDn = 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 { cmdAuthUpdateLdapBindDn = 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 { cmdAuthAddLdapSimpleAuth = 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 { cmdAuthUpdateLdapSimpleAuth = 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(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(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,39 +327,45 @@ 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
} }
return a.createAuthSource(ctx, authSource) return a.createAuthSource(authSource)
} }
// 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
} }
authSource, err := a.getAuthSource(ctx, c, auth.LDAP) authSource, err := a.getAuthSource(c, auth.LDAP)
if err != nil { if err != nil {
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
} }
return a.updateAuthSource(ctx, authSource) return a.updateAuthSource(authSource)
} }
// 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,29 +378,32 @@ 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
} }
return a.createAuthSource(ctx, authSource) return a.createAuthSource(authSource)
} }
// updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. // updateLdapBindDn 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
} }
authSource, err := a.getAuthSource(ctx, c, auth.DLDAP) authSource, err := a.getAuthSource(c, auth.DLDAP)
if err != nil { if err != nil {
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
} }
return a.updateAuthSource(ctx, authSource) return a.updateAuthSource(authSource)
} }

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"
) )
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
{ {
@ -223,28 +210,27 @@ func TestAddLdapBindDn(t *testing.T) {
initDB: func(context.Context) error { initDB: func(context.Context) error {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(authSource *auth.Source) error {
createdAuthSource = authSource createdAuthSource = authSource
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(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(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 = cmdAuthAddLdapBindDn.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
{ {
@ -453,28 +441,27 @@ func TestAddLdapSimpleAuth(t *testing.T) {
initDB: func(context.Context) error { initDB: func(context.Context) error {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(authSource *auth.Source) error {
createdAuthSource = authSource createdAuthSource = authSource
return nil return nil
}, },
updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { updateAuthSource: func(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(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 = cmdAuthAddLdapSimpleAuth.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
{ {
@ -921,15 +896,15 @@ func TestUpdateLdapBindDn(t *testing.T) {
initDB: func(context.Context) error { initDB: func(context.Context) error {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(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(authSource *auth.Source) error {
updatedAuthSource = authSource updatedAuthSource = authSource
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(id int64) (*auth.Source, error) {
if c.id != 0 { if c.id != 0 {
assert.Equal(t, c.id, id, "case %d: wrong id", n) assert.Equal(t, c.id, id, "case %d: wrong id", n)
} }
@ -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 = cmdAuthUpdateLdapBindDn.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
{ {
@ -1309,15 +1286,15 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
initDB: func(context.Context) error { initDB: func(context.Context) error {
return nil return nil
}, },
createAuthSource: func(ctx context.Context, authSource *auth.Source) error { createAuthSource: func(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(authSource *auth.Source) error {
updatedAuthSource = authSource updatedAuthSource = authSource
return nil return nil
}, },
getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { getAuthSourceByID: func(id int64) (*auth.Source, error) {
if c.id != 0 { if c.id != 0 {
assert.Equal(t, c.id, id, "case %d: wrong id", n) assert.Equal(t, c.id, id, "case %d: wrong id", n)
} }
@ -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 = cmdAuthUpdateLdapSimpleAuth.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

@ -1,306 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"net/url"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v3"
)
func oauthCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
&cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
&cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
&cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
&cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
&cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-tenant-id",
Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
&cli.StringFlag{
Name: "icon-url",
Value: "",
Usage: "Custom icon URL for OAuth2 login source",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
&cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
&cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
&cli.StringFlag{
Name: "required-claim-value",
Value: "",
Usage: "Claim value that has to be set to allow users to login with this source",
},
&cli.StringFlag{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
&cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
&cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
&cli.StringFlag{
Name: "group-team-map",
Value: "",
Usage: "JSON mapping between groups and org teams",
},
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups",
},
}
}
func microcmdAuthAddOauth() *cli.Command {
return &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runAddOauth(ctx, cmd)
},
Flags: oauthCLIFlags(),
}
}
func microcmdAuthUpdateOauth() *cli.Command {
return &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
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 {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: c.String("custom-token-url"),
AuthURL: c.String("custom-auth-url"),
ProfileURL: c.String("custom-profile-url"),
EmailURL: c.String("custom-email-url"),
Tenant: c.String("custom-tenant-id"),
}
} else {
customURLMapping = nil
}
return &oauth2.Source{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
GroupClaimName: c.String("group-claim-name"),
AdminGroup: c.String("admin-group"),
RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
}
}
func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
config := parseOAuth2Config(c)
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
Cfg: config,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := a.initDB(ctx); err != nil {
return err
}
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
oAuth2Config := source.Cfg.(*oauth2.Source)
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("provider") {
oAuth2Config.Provider = c.String("provider")
}
if c.IsSet("key") {
oAuth2Config.ClientID = c.String("key")
}
if c.IsSet("secret") {
oAuth2Config.ClientSecret = c.String("secret")
}
if c.IsSet("auto-discover-url") {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
if c.IsSet("icon-url") {
oAuth2Config.IconURL = c.String("icon-url")
}
if c.IsSet("scopes") {
oAuth2Config.Scopes = c.StringSlice("scopes")
}
if c.IsSet("required-claim-name") {
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
}
if c.IsSet("required-claim-value") {
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
}
if c.IsSet("group-claim-name") {
oAuth2Config.GroupClaimName = c.String("group-claim-name")
}
if c.IsSet("admin-group") {
oAuth2Config.AdminGroup = c.String("admin-group")
}
if c.IsSet("restricted-group") {
oAuth2Config.RestrictedGroup = c.String("restricted-group")
}
if c.IsSet("group-team-map") {
oAuth2Config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
// update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{}
if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
customURLMapping.TokenURL = c.String("custom-token-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
customURLMapping.AuthURL = c.String("custom-auth-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
customURLMapping.ProfileURL = c.String("custom-profile-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
customURLMapping.EmailURL = c.String("custom-email-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") {
customURLMapping.Tenant = c.String("custom-tenant-id")
}
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(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,200 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"strings"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/smtp"
"github.com/urfave/cli/v3"
)
func smtpCLIFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
&cli.StringFlag{
Name: "auth-type",
Value: "PLAIN",
Usage: "SMTP Authentication Type (PLAIN/LOGIN/CRAM-MD5) default PLAIN",
},
&cli.StringFlag{
Name: "host",
Value: "",
Usage: "SMTP Host",
},
&cli.IntFlag{
Name: "port",
Usage: "SMTP Port",
},
&cli.BoolFlag{
Name: "force-smtps",
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
},
&cli.BoolFlag{
Name: "skip-verify",
Usage: "Skip TLS verify.",
},
&cli.StringFlag{
Name: "helo-hostname",
Value: "",
Usage: "Hostname sent with HELO. Leave blank to send current hostname",
},
&cli.BoolFlag{
Name: "disable-helo",
Usage: "Disable SMTP helo.",
},
&cli.StringFlag{
Name: "allowed-domains",
Value: "",
Usage: "Leave empty to allow all domains. Separate multiple domains with a comma (',')",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Skip 2FA to log on.",
},
&cli.BoolFlag{
Name: "active",
Usage: "This Authentication Source is Activated.",
Value: true,
},
}
}
func microcmdAuthUpdateSMTP() *cli.Command {
return &cli.Command{
Name: "update-smtp",
Usage: "Update existing SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
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 {
return &cli.Command{
Name: "add-smtp",
Usage: "Add new SMTP authentication source",
Action: func(ctx context.Context, cmd *cli.Command) error {
return newAuthService().runAddSMTP(ctx, cmd)
},
Flags: smtpCLIFlags(),
}
}
func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error {
if c.IsSet("auth-type") {
conf.Auth = c.String("auth-type")
validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"}
if !util.SliceContainsString(validAuthTypes, strings.ToUpper(c.String("auth-type"))) {
return errors.New("Auth must be one of PLAIN/LOGIN/CRAM-MD5")
}
conf.Auth = c.String("auth-type")
}
if c.IsSet("host") {
conf.Host = c.String("host")
}
if c.IsSet("port") {
conf.Port = c.Int("port")
}
if c.IsSet("allowed-domains") {
conf.AllowedDomains = c.String("allowed-domains")
}
if c.IsSet("force-smtps") {
conf.ForceSMTPS = c.Bool("force-smtps")
}
if c.IsSet("skip-verify") {
conf.SkipVerify = c.Bool("skip-verify")
}
if c.IsSet("helo-hostname") {
conf.HeloHostname = c.String("helo-hostname")
}
if c.IsSet("disable-helo") {
conf.DisableHelo = c.Bool("disable-helo")
}
return nil
}
func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error {
if err := a.initDB(ctx); err != nil {
return err
}
if !c.IsSet("name") || len(c.String("name")) == 0 {
return errors.New("name must be set")
}
if !c.IsSet("host") || len(c.String("host")) == 0 {
return errors.New("host must be set")
}
if !c.IsSet("port") {
return errors.New("port must be set")
}
active := true
if c.IsSet("active") {
active = c.Bool("active")
}
var smtpConfig smtp.Source
if err := parseSMTPConfig(c, &smtpConfig); err != nil {
return err
}
// If not set default to PLAIN
if len(smtpConfig.Auth) == 0 {
smtpConfig.Auth = "PLAIN"
}
return a.createAuthSource(ctx, &auth_model.Source{
Type: auth_model.SMTP,
Name: c.String("name"),
IsActive: active,
Cfg: &smtpConfig,
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error {
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
if err := a.initDB(ctx); err != nil {
return err
}
source, err := a.getAuthSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
smtpConfig := source.Cfg.(*smtp.Source)
if err := parseSMTPConfig(c, smtpConfig); err != nil {
return err
}
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("active") {
source.IsActive = c.Bool("active")
}
source.Cfg = smtpConfig
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return a.updateAuthSource(ctx, source)
}

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

@ -1,42 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"code.gitea.io/gitea/modules/graceful"
asymkey_service "code.gitea.io/gitea/services/asymkey"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/urfave/cli/v3"
)
var (
microcmdRegenHooks = &cli.Command{
Name: "hooks",
Usage: "Regenerate git-hooks",
Action: runRegenerateHooks,
}
microcmdRegenKeys = &cli.Command{
Name: "keys",
Usage: "Regenerate authorized_keys file",
Action: runRegenerateKeys,
}
)
func runRegenerateHooks(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
}
func runRegenerateKeys(ctx context.Context, _ *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
return asymkey_service.RewriteAllPublicKeys(ctx)
}

View file

@ -4,18 +4,18 @@
package cmd package cmd
import ( import (
"github.com/urfave/cli/v3" "github.com/urfave/cli"
) )
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

@ -9,68 +9,66 @@ import (
"fmt" "fmt"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
user_service "code.gitea.io/gitea/services/user"
"github.com/urfave/cli/v3" "github.com/urfave/cli"
) )
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,u",
Name: "username", Value: "",
Aliases: []string{"u"}, 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,p",
Value: "",
Usage: "New password to set for user",
},
},
} }
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
}
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err return err
} }
opts := &user_service.UpdateAuthOptions{ ctx, cancel := installSignals()
Password: optional.Some(c.String("password")), defer cancel()
MustChangePassword: optional.Some(c.Bool("must-change-password")),
if err := initDB(ctx); err != nil {
return err
} }
if err := user_service.UpdateAuth(ctx, user, opts); err != nil { if len(c.String("password")) < setting.MinPasswordLength {
switch { return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
case errors.Is(err, password.ErrMinLength): }
return fmt.Errorf("password is not long enough, needs to be at least %d characters", setting.MinPasswordLength)
case errors.Is(err, password.ErrComplexity): if !pwd.IsComplexEnough(c.String("password")) {
return errors.New("password does not meet complexity requirements") return errors.New("Password does not meet complexity requirements")
case errors.Is(err, password.ErrIsPwned): }
return errors.New("the password is in a list of stolen passwords previously exposed in public data breaches, please try again with a different password, to see more details: https://haveibeenpwned.com/Passwords") pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
default: if err != nil {
return err return err
} }
if pwned {
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
}
uname := c.String("username")
user, err := user_model.GetUserByName(ctx, uname)
if err != nil {
return err
}
if err = user.SetPassword(c.String("password")); err != nil {
return err
}
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
return err
} }
fmt.Printf("%s's password has been successfully updated!\n", user.Name) fmt.Printf("%s's password has been successfully updated!\n", user.Name)

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

@ -4,122 +4,78 @@
package cmd package cmd
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "os"
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
pwd "code.gitea.io/gitea/modules/auth/password" pwd "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional"
"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"
) )
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: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: 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.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 if err := argsSet(c, "email"); err != nil {
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. return err
setting.LoadSettings() }
userTypes := map[string]user_model.UserType{ if c.IsSet("name") && c.IsSet("username") {
"individual": user_model.UserTypeIndividual, return errors.New("Cannot set both --name and --username flags")
"bot": user_model.UserTypeBot,
} }
userType, ok := userTypes[c.String("user-type")] if !c.IsSet("name") && !c.IsSet("username") {
if !ok { return errors.New("One of --name or --username flags must be set")
return fmt.Errorf("invalid user type: %s", c.String("user-type"))
}
if userType != user_model.UserTypeIndividual {
// Some other commands like "change-password" also only support individual users.
// 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,15 +87,14 @@ 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(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
} }
if !setting.IsInTesting { ctx, cancel := installSignals()
// FIXME: need to refactor the "initDB" related code later defer cancel()
// it doesn't make sense to call it in (almost) every command action function
if err := initDB(ctx); err != nil { if err := initDB(ctx); err != nil {
return err return err
}
} }
var password string var password string
@ -152,34 +107,27 @@ 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") // always default to true
mustChangePassword := true // always default to true changePassword := true
if c.IsSet("must-change-password") {
if userType != user_model.UserTypeIndividual { // If this is the first user being created.
return errors.New("must-change-password flag can only be set for individual users") // Take it as the admin and don't force a password update.
} if n := user_model.CountUsers(nil); n == 0 {
// if the flag is set, use the value provided by the user changePassword = false
mustChangePassword = c.Bool("must-change-password")
} else if userType == user_model.UserTypeIndividual {
// check whether there are users in the database
hasUserRecord, err := db.IsTableNotEmpty(&user_model.User{})
if err != nil {
return fmt.Errorf("IsTableNotEmpty: %w", err)
}
if !hasUserRecord {
// if this is the first one being created, don't force to change password (keep the old behavior)
mustChangePassword = false
}
} }
restricted := optional.None[bool]() if c.IsSet("must-change-password") {
changePassword = c.Bool("must-change-password")
}
restricted := util.OptionalBoolNone
if c.IsSet("restricted") { if c.IsSet("restricted") {
restricted = optional.Some(c.Bool("restricted")) restricted = util.OptionalBoolOf(c.Bool("restricted"))
} }
// default user visibility in app.ini // default user visibility in app.ini
@ -188,53 +136,34 @@ 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,
MustChangePassword: mustChangePassword, IsAdmin: c.Bool("admin"),
MustChangePassword: changePassword,
Visibility: visibility, Visibility: visibility,
FullName: c.String("fullname"),
} }
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: optional.Some(true), IsActive: util.OptionalBoolTrue,
IsRestricted: restricted, IsRestricted: restricted,
} }
var accessTokenName string if err := user_model.CreateUser(u, overwriteDefault); err != nil {
var accessTokenScope auth_model.AccessTokenScope
if c.IsSet("access-token") {
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
if accessTokenName == "" {
return errors.New("access-token-name cannot be empty")
}
var err error
accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err)
}
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
}
// arguments should be prepared before creating the user & access token, in case there is anything wrong
// create the user
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
return fmt.Errorf("CreateUser: %w", err) return fmt.Errorf("CreateUser: %w", err)
} }
fmt.Printf("New user '%s' has been successfully created!\n", username)
// create the access token if c.Bool("access-token") {
if accessTokenScope != "" { t := &auth_model.AccessToken{
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} Name: "gitea-admin",
if err := auth_model.NewAccessToken(ctx, t); err != nil { UID: u.ID,
}
if err := auth_model.NewAccessToken(t); err != nil {
return err return err
} }
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

@ -1,134 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAdminUserCreate(t *testing.T) {
reset := 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{}))
}
t.Run("MustChangePassword", func(t *testing.T) {
type check struct {
IsAdmin bool
MustChangePassword bool
}
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))))
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword}
}
reset()
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
reset()
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
reset()
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
})
createUser := func(name string, args ...string) error {
return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, 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) {
// no generated access token
reset()
assert.NoError(t, createUser("u", "--random-password"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
// using "--access-token" only means "all" access
reset()
assert.NoError(t, createUser("u", "--random-password", "--access-token"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
assert.NoError(t, err)
assert.True(t, hasScopes)
// using "--access-token" with name & scopes
reset()
assert.NoError(t, createUser("u", "--random-password", "--access-token", "--access-token-name", "new-token-name", "--access-token-scopes", "read:issue,read:user"))
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
assert.NoError(t, err)
assert.True(t, hasScopes)
hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
assert.NoError(t, err)
assert.False(t, hasScopes)
// using "--access-token-name" without "--access-token"
reset()
err = createUser("u", "--random-password", "--access-token-name", "new-token-name")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
// using "--access-token-scopes" without "--access-token"
reset()
err = createUser("u", "--random-password", "--access-token-scopes", "read:issue")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
// empty permission
reset()
err = createUser("u", "--random-password", "--access-token", "--access-token-scopes", "public-only")
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
assert.ErrorContains(t, err, "access token does not have any permission")
})
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,50 @@
package cmd package cmd
import ( import (
"context"
"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"
) )
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,u",
Usage: "Username of the user to delete",
},
cli.StringFlag{
Name: "email,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 fmt.Errorf("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 +67,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,49 +4,48 @@
package cmd package cmd
import ( import (
"context"
"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"
) )
var microcmdUserGenerateAccessToken = &cli.Command{ var microcmdUserGenerateAccessToken = cli.Command{
Name: "generate-access-token", Name: "generate-access-token",
Usage: "Generate an access token for a specific user", Usage: "Generate an access token for a specific user",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ cli.StringFlag{
Name: "username", Name: "username,u",
Aliases: []string{"u"}, Usage: "Username",
Usage: "Username",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "token-name", Name: "token-name,t",
Aliases: []string{"t"}, Usage: "Token name",
Usage: "Token name", Value: "gitea-admin",
Value: "gitea-admin",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "raw", Name: "raw",
Usage: "Display only the token value", Usage: "Display only the token value",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "scopes", Name: "scopes",
Value: "all", Value: "",
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, Usage: "Comma separated list of scopes to apply to access token",
}, },
}, },
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 fmt.Errorf("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
} }
@ -62,12 +61,12 @@ func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
UID: user.ID, UID: user.ID,
} }
exist, err := auth_model.AccessTokenByNameExists(ctx, t) exist, err := auth_model.AccessTokenByNameExists(t)
if err != nil { if err != nil {
return err return err
} }
if exist { if exist {
return errors.New("access token name has been used already") return fmt.Errorf("access token name has been used already")
} }
// make sure the scopes are valid // make sure the scopes are valid
@ -75,13 +74,10 @@ func runGenerateAccessToken(ctx context.Context, c *cli.Command) error {
if err != nil { if err != nil {
return fmt.Errorf("invalid access token scope provided: %w", err) return fmt.Errorf("invalid access token scope provided: %w", err)
} }
if !accessTokenScope.HasPermissionScope() {
return errors.New("access token does not have any permission")
}
t.Scope = accessTokenScope t.Scope = accessTokenScope
// create the token // create the token
if err := auth_model.NewAccessToken(ctx, t); err != nil { if err := auth_model.NewAccessToken(t); err != nil {
return err return err
} }

View file

@ -4,34 +4,36 @@
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"
) )
var microcmdUserList = &cli.Command{ var microcmdUserList = cli.Command{
Name: "list", Name: "list",
Usage: "List users", Usage: "List users",
Action: runListUsers, Action: runListUsers,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "admin", Name: "admin",
Usage: "List only admin users", Usage: "List only admin users",
}, },
}, },
} }
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
} }
users, err := user_model.GetAllUsers(ctx) users, err := user_model.GetAllUsers()
if err != nil { if err != nil {
return err return err
} }
@ -46,7 +48,7 @@ func runListUsers(ctx context.Context, c *cli.Command) error {
} }
} }
} else { } else {
twofa := user_model.UserList(users).GetTwoFaStatus(ctx) twofa := user_model.UserList(users).GetTwoFaStatus()
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
for _, u := range users { for _, u := range users {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID]) fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])

View file

@ -4,41 +4,38 @@
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"
) )
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,A",
Name: "all", Usage: "All users must change password, except those explicitly excluded with --exclude",
Aliases: []string{"A"},
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,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,13 +44,11 @@ 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(), exclude)
if err != nil { if err != nil {
return err return err
} }

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"
) )
// 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: 2048,
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"
) )
// 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,37 +109,18 @@ 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 {
for _, ctx := range c.Lineage() {
if ctx.Bool(name) {
return true
}
}
return false
}
// 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 c.Bool("quiet") || c.GlobalBoolT("quiet") {
level = log.FATAL level = log.FATAL
} }
if globalBool(c, "debug") || globalBool(c, "verbose") { if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") {
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)
}

56
cmd/convert.go Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli"
)
// CmdConvert represents the available convert sub-command.
var CmdConvert = cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
Action: runConvert,
}
func runConvert(ctx *cli.Context) error {
stdCtx, cancel := installSignals()
defer cancel()
if err := initDB(stdCtx); err != nil {
return err
}
log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf)
switch {
case setting.Database.Type.IsMySQL():
if err := db.ConvertUtf8ToUtf8mb4(); err != nil {
log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")
case setting.Database.Type.IsMSSQL():
if err := db.ConvertVarcharToNVarchar(); err != nil {
log.Fatal("Failed to convert database from varchar to nvarchar: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now")
default:
fmt.Println("This command can only be used with a MySQL or MSSQL database")
}
return nil
}

View file

@ -4,17 +4,15 @@
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"
"github.com/urfave/cli/v3"
) )
// CmdDocs represents the available docs sub-command. // CmdDocs represents the available docs sub-command.
var CmdDocs = &cli.Command{ var CmdDocs = cli.Command{
Name: "docs", Name: "docs",
Usage: "Output CLI documentation", Usage: "Output CLI documentation",
Description: "A command to output Gitea's CLI documentation, optionally to a file.", Description: "A command to output Gitea's CLI documentation, optionally to a file.",
@ -25,23 +23,22 @@ var CmdDocs = &cli.Command{
Usage: "Output man pages instead", Usage: "Output man pages instead",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "output", Name: "output, o",
Aliases: []string{"o"}, Usage: "Path to output to instead of stdout (will overwrite if exists)",
Usage: "Path to output to instead of stdout (will overwrite if exists)",
}, },
}, },
} }
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 +50,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"
@ -15,72 +14,61 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
migrate_base "code.gitea.io/gitea/models/migrations/base" migrate_base "code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/doctor"
"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/doctor"
"github.com/urfave/cli/v3" "github.com/urfave/cli"
"xorm.io/xorm" "xorm.io/xorm"
) )
// CmdDoctor represents the available doctor sub-command. // CmdDoctor represents the available doctor sub-command.
var CmdDoctor = &cli.Command{ var CmdDoctor = cli.Command{
Name: "doctor", Name: "doctor",
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.",
Commands: []*cli.Command{
cmdDoctorCheck,
cmdRecreateTable,
cmdDoctorConvert,
},
}
var cmdDoctorCheck = &cli.Command{
Name: "check",
Usage: "Diagnose and optionally fix problems", Usage: "Diagnose and optionally fix problems",
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.",
Action: runDoctorCheck, Action: runDoctor,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "list", Name: "list",
Usage: "List the available checks", Usage: "List the available checks",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "default", Name: "default",
Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)",
}, },
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "run", Name: "run",
Usage: "Run the provided checks - (if --default is set, the default checks will also run)", Usage: "Run the provided checks - (if --default is set, the default checks will also run)",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "all", Name: "all",
Usage: "Run all the available checks", Usage: "Run all the available checks",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "fix", Name: "fix",
Usage: "Automatically fix what we can", Usage: "Automatically fix what we can",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "log-file", Name: "log-file",
Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`, Usage: `Name of the log file (default: "doctor.log"). Set to "-" to output to stdout, set to "" to disable`,
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "color", Name: "color, H",
Aliases: []string{"H"}, Usage: "Use color for outputted information",
Usage: "Use color for outputted information",
}, },
}, },
Subcommands: []cli.Command{
cmdRecreateTable,
},
} }
var cmdRecreateTable = &cli.Command{ var cmdRecreateTable = cli.Command{
Name: "recreate-table", Name: "recreate-table",
Usage: "Recreate tables from XORM definitions and copy the data.", Usage: "Recreate tables from XORM definitions and copy the data.",
ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "Print SQL commands sent", Usage: "Print SQL commands sent",
}, },
@ -93,13 +81,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 +101,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 +119,31 @@ 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 !ctx.IsSet("log-file") {
case "": logFile = "doctor.log"
return // if no doctor log-file is set, do not show any log from default logger }
case "-":
if len(logFile) == 0 {
// if no doctor log-file is set, do not show any log from default logger
return
}
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,23 +155,25 @@ func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) {
} }
} }
func runDoctorCheck(ctx context.Context, cmd *cli.Command) error { func runDoctor(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)
for _, check := range doctor.Checks { for _, check := range doctor.Checks {
if check.IsDefault { if check.IsDefault {
_, _ = w.Write([]byte{'*'}) _, _ = w.Write([]byte{'*'})
@ -189,20 +188,26 @@ 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 = doctor.Checks
copy(checks, doctor.Checks) } else if ctx.IsSet("run") {
} else if cmd.IsSet("run") { addDefault := ctx.Bool("default")
addDefault := cmd.Bool("default") names := ctx.StringSlice("run")
runNamesSet := container.SetOf(cmd.StringSlice("run")...) for i, name := range names {
for _, check := range doctor.Checks { names[i] = strings.ToLower(strings.TrimSpace(name))
if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) {
checks = append(checks, check)
runNamesSet.Remove(check.Name)
}
} }
if len(runNamesSet) > 0 {
return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ",")) for _, check := range doctor.Checks {
if addDefault && check.IsDefault {
checks = append(checks, check)
continue
}
for _, name := range names {
if name == check.Name {
checks = append(checks, check)
break
}
}
} }
} else { } else {
for _, check := range doctor.Checks { for _, check := range doctor.Checks {
@ -211,5 +216,6 @@ 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

@ -1,54 +0,0 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/urfave/cli/v3"
)
// cmdDoctorConvert represents the available convert sub-command.
var cmdDoctorConvert = &cli.Command{
Name: "convert",
Usage: "Convert the database",
Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar",
Action: runDoctorConvert,
}
func runDoctorConvert(ctx context.Context, cmd *cli.Command) error {
if err := initDB(ctx); err != nil {
return err
}
log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.Log.RootPath)
log.Info("Configuration file: %s", setting.CustomConf)
switch {
case setting.Database.Type.IsMySQL():
if err := db.ConvertDatabaseTable(); err != nil {
log.Fatal("Failed to convert database & table: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4")
case setting.Database.Type.IsMSSQL():
if err := db.ConvertVarcharToNVarchar(); err != nil {
log.Fatal("Failed to convert database from varchar to nvarchar: %v", err)
return err
}
fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now")
default:
fmt.Println("This command can only be used with a MySQL or MSSQL database")
}
return nil
}

View file

@ -1,34 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"testing"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/doctor"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v3"
)
func TestDoctorRun(t *testing.T) {
doctor.Register(&doctor.Check{
Title: "Test Check",
Name: "test-check",
Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil },
SkipDatabaseInitialization: true,
})
app := &cli.Command{
Commands: []*cli.Command{cmdDoctorCheck},
}
err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"})
assert.NoError(t, err)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"})
assert.ErrorContains(t, err, `unknown checks: "no-such"`)
}

View file

@ -5,14 +5,15 @@
package cmd package cmd
import ( import (
"context" "fmt"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/dump"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -21,157 +22,245 @@ 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"
) )
func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error {
if verbose {
log.Info("Adding file %s", customName)
}
return w.Write(archiver.File{
FileInfo: archiver.FileInfo{
FileInfo: info,
CustomName: customName,
},
ReadCloser: r,
})
}
func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error {
file, err := os.Open(absPath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
return addReader(w, file, fileInfo, filePath, verbose)
}
func isSubdir(upper, lower string) (bool, error) {
if relPath, err := filepath.Rel(upper, lower); err != nil {
return false, err
} else if relPath == "." || !strings.HasPrefix(relPath, ".") {
return true, nil
}
return false, nil
}
type outputType struct {
Enum []string
Default string
selected string
}
func (o outputType) Join() string {
return strings.Join(o.Enum, ", ")
}
func (o *outputType) Set(value string) error {
for _, enum := range o.Enum {
if enum == value {
o.selected = value
return nil
}
}
return fmt.Errorf("allowed values are %s", o.Join())
}
func (o outputType) String() string {
if o.selected == "" {
return o.Default
}
return o.selected
}
var outputTypeEnum = &outputType{
Enum: []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"},
Default: "zip",
}
// CmdDump represents the available dump sub-command. // CmdDump represents the available dump sub-command.
var CmdDump = &cli.Command{ var CmdDump = cli.Command{
Name: "dump", Name: "dump",
Usage: "Dump Gitea files and database", Usage: "Dump Gitea files and database",
Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`, Description: `Dump compresses all related files and database into zip file.
Action: runDump, It can be used for backup and capture Gitea server image to send to maintainer`,
Action: runDump,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ cli.StringFlag{
Name: "file", Name: "file, f",
Aliases: []string{"f"}, Value: fmt.Sprintf("gitea-dump-%d.zip", time.Now().Unix()),
Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`, Usage: "Name of the dump file which will be created. Supply '-' for stdout. See type for available types.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "verbose", Name: "verbose, V",
Aliases: []string{"V"}, Usage: "Show process details",
Usage: "Show process details",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "quiet", Name: "quiet, q",
Aliases: []string{"q"}, Usage: "Only display warnings and errors",
Usage: "Only display warnings and errors",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "tempdir", Name: "tempdir, t",
Aliases: []string{"t"}, Value: os.TempDir(),
Value: os.TempDir(), Usage: "Temporary dir path",
Usage: "Temporary dir path",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "database", Name: "database, d",
Aliases: []string{"d"}, Usage: "Specify the database SQL syntax",
Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-repository", Name: "skip-repository, R",
Aliases: []string{"R"}, Usage: "Skip the repository dumping",
Usage: "Skip the repository dumping",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-log", Name: "skip-log, L",
Aliases: []string{"L"}, Usage: "Skip the log dumping",
Usage: "Skip the log dumping",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-custom-dir", Name: "skip-custom-dir",
Usage: "Skip custom directory", Usage: "Skip custom directory",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-lfs-data", Name: "skip-lfs-data",
Usage: "Skip LFS data", Usage: "Skip LFS data",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-attachment-data", Name: "skip-attachment-data",
Usage: "Skip attachment data", Usage: "Skip attachment data",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-package-data", Name: "skip-package-data",
Usage: "Skip package data", Usage: "Skip package data",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "skip-index", Name: "skip-index",
Usage: "Skip bleve index data", Usage: "Skip bleve index data",
}, },
&cli.BoolFlag{ cli.GenericFlag{
Name: "skip-db",
Usage: "Skip database",
},
&cli.StringFlag{
Name: "type", Name: "type",
Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), Value: outputTypeEnum,
Usage: fmt.Sprintf("Dump output format: %s", outputTypeEnum.Join()),
}, },
}, },
} }
func fatal(format string, args ...any) { func fatal(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
log.Fatal(format, args...) log.Fatal(format, args...)
} }
func runDump(ctx context.Context, cmd *cli.Command) error { func runDump(ctx *cli.Context) error {
var file *os.File
fileName := ctx.String("file")
outType := ctx.String("type")
if fileName == "-" {
file = os.Stdout
setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr)
} else {
for _, suffix := range outputTypeEnum.Enum {
if strings.HasSuffix(fileName, "."+suffix) {
fileName = strings.TrimSuffix(fileName, "."+suffix)
break
}
}
fileName += "." + outType
}
setting.MustInstalled() setting.MustInstalled()
quite := cmd.Bool("quiet") // make sure we are logging to the console no matter what the configuration tells us do to
verbose := cmd.Bool("verbose") // FIXME: don't use CfgProvider directly
if verbose && quite { if _, err := setting.CfgProvider.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Option --quiet and --verbose cannot both be set") fatal("Setting logging mode to console failed: %v", err)
}
if _, err := setting.CfgProvider.Section("log.console").NewKey("STDERR", "true"); err != nil {
fatal("Setting console logger to stderr failed: %v", err)
} }
// outFileName is either "-" or a file name (will be made absolute) // Set loglevel to Warn if quiet-mode is requested
outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type")) if ctx.Bool("quiet") {
if outType == "" { if _, err := setting.CfgProvider.Section("log.console").NewKey("LEVEL", "Warn"); err != nil {
fatal("Invalid output type") fatal("Setting console log-level failed: %v", err)
}
} }
outFile := os.Stdout if !setting.InstallLock {
if outFileName != "-" { log.Error("Is '%s' really the right config path?\n", setting.CustomConf)
var err error return fmt.Errorf("gitea is not initialized")
if outFileName, err = filepath.Abs(outFileName); err != nil {
fatal("Unable to get absolute path of dump file: %v", err)
}
if exist, _ := util.IsExist(outFileName); exist {
fatal("Dump file %q exists", outFileName)
}
if outFile, err = os.Create(outFileName); err != nil {
fatal("Unable to create dump file %q: %v", outFileName, err)
}
defer outFile.Close()
} }
setupConsoleLogger(util.Iif(quite, log.WARN, log.INFO), log.CanColorStderr, os.Stderr)
setting.DisableLoggerInit()
setting.LoadSettings() // cannot access session settings otherwise setting.LoadSettings() // cannot access session settings otherwise
err := db.InitEngine(ctx) verbose := ctx.Bool("verbose")
if verbose && ctx.Bool("quiet") {
return fmt.Errorf("--quiet and --verbose cannot both be set")
}
stdCtx, cancel := installSignals()
defer cancel()
err := db.InitEngine(stdCtx)
if err != nil { if err != nil {
return err return err
} }
if err = storage.Init(); err != nil { if err := storage.Init(); err != nil {
return err return err
} }
archiverGeneric, err := archiver.ByExtension("." + outType) if file == nil {
file, err = os.Create(fileName)
if err != nil {
fatal("Unable to open %s: %v", fileName, err)
}
}
defer file.Close()
absFileName, err := filepath.Abs(fileName)
if err != nil {
return err
}
var iface any
if fileName == "-" {
iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType))
} else {
iface, err = archiver.ByExtension(fileName)
}
if err != nil { if err != nil {
fatal("Unable to get archiver for extension: %v", err) fatal("Unable to get archiver for extension: %v", err)
} }
archiverWriter := archiverGeneric.(archiver.Writer) w, _ := iface.(archiver.Writer)
if err := archiverWriter.Create(outFile); err != nil { if err := w.Create(file); err != nil {
fatal("Creating archiver.Writer failed: %v", err) fatal("Creating archiver.Writer failed: %v", err)
} }
defer archiverWriter.Close() defer w.Close()
dumper := &dump.Dumper{ if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") {
Writer: archiverWriter,
Verbose: verbose,
}
dumper.GlobalExcludeAbsPath(outFileName)
if cmd.IsSet("skip-repository") && cmd.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)
if err := dumper.AddRecursiveExclude("repos", setting.RepoRootPath, nil); err != nil { if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil {
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")
@ -180,61 +269,58 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "lfs", objPath))
return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose)
}); err != nil { }); err != nil {
fatal("Failed to dump LFS objects: %v", err) fatal("Failed to dump LFS objects: %v", err)
} }
} }
if cmd.Bool("skip-db") { tmpDir := ctx.String("tempdir")
// Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
dumper.GlobalExcludeAbsPath(setting.Database.Path) fatal("Path does not exist: %s", tmpDir)
log.Info("Skipping database") }
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql")
if err != nil {
fatal("Failed to create tmp file: %v", err)
}
defer func() {
_ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
}
}()
targetDBType := ctx.String("database")
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() {
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType)
} else { } else {
tmpDir := cmd.String("tempdir") log.Info("Dumping database...")
if _, err := os.Stat(tmpDir); os.IsNotExist(err) { }
fatal("Path does not exist: %s", tmpDir)
}
dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql") if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil {
if err != nil { fatal("Failed to dump database: %v", err)
fatal("Failed to create tmp file: %v", err) }
}
defer func() {
_ = dbDump.Close()
if err := util.Remove(dbDump.Name()); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
}
}()
targetDBType := cmd.String("database") if err := addFile(w, "gitea-db.sql", dbDump.Name(), verbose); err != nil {
if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { fatal("Failed to include gitea-db.sql: %v", err)
log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) }
} else {
log.Info("Dumping database...")
}
if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { if len(setting.CustomConf) > 0 {
fatal("Failed to dump database: %v", err) log.Info("Adding custom configuration file from %s", setting.CustomConf)
} if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil {
fatal("Failed to include specified app.ini: %v", err)
if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil {
fatal("Failed to include gitea-db.sql: %v", err)
} }
} }
log.Info("Adding custom configuration file from %s", setting.CustomConf) if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") {
if err = dumper.AddFile("app.ini", setting.CustomConf); err != nil {
fatal("Failed to include specified app.ini: %v", err)
}
if cmd.IsSet("skip-custom-dir") && cmd.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)
if err == nil && customDir.IsDir() { if err == nil && customDir.IsDir() {
if is, _ := dump.IsSubdir(setting.AppDataPath, setting.CustomPath); !is { if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is {
if err := dumper.AddRecursiveExclude("custom", setting.CustomPath, nil); err != nil { if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil {
fatal("Failed to include custom: %v", err) fatal("Failed to include custom: %v", err)
} }
} else { } else {
@ -261,7 +347,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)
} }
@ -271,24 +357,26 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
excludes = append(excludes, setting.Attachment.Storage.Path) excludes = append(excludes, setting.Attachment.Storage.Path)
excludes = append(excludes, setting.Packages.Storage.Path) excludes = append(excludes, setting.Packages.Storage.Path)
excludes = append(excludes, setting.Log.RootPath) excludes = append(excludes, setting.Log.RootPath)
if err := dumper.AddRecursiveExclude("data", setting.AppDataPath, excludes); err != nil { excludes = append(excludes, absFileName)
if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil {
fatal("Failed to include data directory: %v", err) fatal("Failed to include data directory: %v", err)
} }
} }
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()
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "attachments", objPath))
return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose)
}); err != nil { }); err != nil {
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")
@ -297,7 +385,8 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
if err != nil { if err != nil {
return err return err
} }
return dumper.AddReader(object, info, path.Join("data", "packages", objPath))
return addReader(w, object, info, path.Join("data", "packages", objPath), verbose)
}); err != nil { }); err != nil {
fatal("Failed to dump packages: %v", err) fatal("Failed to dump packages: %v", err)
} }
@ -305,7 +394,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)
@ -313,23 +402,80 @@ func runDump(ctx context.Context, cmd *cli.Command) error {
log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err) log.Error("Unable to check if %s exists. Error: %v", setting.Log.RootPath, err)
} }
if isExist { if isExist {
if err := dumper.AddRecursiveExclude("log", setting.Log.RootPath, nil); err != nil { if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil {
fatal("Failed to include log: %v", err) fatal("Failed to include log: %v", err)
} }
} }
} }
if outFileName == "-" { if fileName != "-" {
log.Info("Finish dumping to stdout") if err = w.Close(); err != nil {
} else { _ = util.Remove(fileName)
if err = archiverWriter.Close(); err != nil { fatal("Failed to save %s: %v", fileName, err)
_ = os.Remove(outFileName)
fatal("Failed to save %q: %v", outFileName, err)
} }
if err = os.Chmod(outFileName, 0o600); err != nil {
if err := os.Chmod(fileName, 0o600); err != nil {
log.Info("Can't change file access permissions mask to 0600: %v", err) log.Info("Can't change file access permissions mask to 0600: %v", err)
} }
log.Info("Finish dumping in file %s", outFileName) }
if fileName != "-" {
log.Info("Finish dumping in file %s", fileName)
} else {
log.Info("Finish dumping to stdout")
}
return nil
}
// addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath
func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error {
absPath, err := filepath.Abs(absPath)
if err != nil {
return err
}
dir, err := os.Open(absPath)
if err != nil {
return err
}
defer dir.Close()
files, err := dir.Readdir(0)
if err != nil {
return err
}
for _, file := range files {
currentAbsPath := path.Join(absPath, file.Name())
currentInsidePath := path.Join(insidePath, file.Name())
if file.IsDir() {
if !util.SliceContainsString(excludeAbsPath, currentAbsPath) {
if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil {
return err
}
if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil {
return err
}
}
} else {
// only copy regular files and symlink regular files, skip non-regular files like socket/pipe/...
shouldAdd := file.Mode().IsRegular()
if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := filepath.EvalSymlinks(currentAbsPath)
if err != nil {
return err
}
targetStat, err := os.Stat(target)
if err != nil {
return err
}
shouldAdd = targetStat.Mode().IsRegular()
}
if shouldAdd {
if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil {
return err
}
}
}
} }
return nil return nil
} }

View file

@ -19,58 +19,57 @@ 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"
) )
// CmdDumpRepository represents the available dump repository sub-command. // CmdDumpRepository represents the available dump repository sub-command.
var CmdDumpRepository = &cli.Command{ var CmdDumpRepository = cli.Command{
Name: "dump-repo", Name: "dump-repo",
Usage: "Dump the repository from git/github/gitea/gitlab", Usage: "Dump the repository from git/github/gitea/gitlab",
Description: "This is a command for dumping the repository data.", Description: "This is a command for dumping the repository data.",
Action: runDumpRepository, Action: runDumpRepository,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ cli.StringFlag{
Name: "git_service", Name: "git_service",
Value: "", Value: "",
Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "repo_dir", Name: "repo_dir, r",
Aliases: []string{"r"}, Value: "./data",
Value: "./data", Usage: "Repository dir path to store the data",
Usage: "Repository dir path to store the data",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "clone_addr", Name: "clone_addr",
Value: "", Value: "",
Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "auth_username", Name: "auth_username",
Value: "", Value: "",
Usage: "The username to visit the clone_addr", Usage: "The username to visit the clone_addr",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "auth_password", Name: "auth_password",
Value: "", Value: "",
Usage: "The password to visit the clone_addr", Usage: "The password to visit the clone_addr",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "auth_token", Name: "auth_token",
Value: "", Value: "",
Usage: "The personal token to visit the clone_addr", Usage: "The personal token to visit the clone_addr",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "owner_name", Name: "owner_name",
Value: "", Value: "",
Usage: "The data will be stored on a directory with owner name if not empty", Usage: "The data will be stored on a directory with owner name if not empty",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "repo_name", Name: "repo_name",
Value: "", Value: "",
Usage: "The data will be stored on a directory with repository name if not empty", Usage: "The data will be stored on a directory with repository name if not empty",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "units", Name: "units",
Value: "", Value: "",
Usage: `Which items will be migrated, one or more units should be separated as comma. Usage: `Which items will be migrated, one or more units should be separated as comma.
@ -79,13 +78,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 +99,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 +118,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 +134,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 +163,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 +178,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,74 +19,70 @@ 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"
) )
// CmdEmbedded represents the available extract sub-command. // CmdEmbedded represents the available extract sub-command.
var ( var (
CmdEmbedded = &cli.Command{ CmdEmbedded = cli.Command{
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,
}, },
} }
subcmdList = &cli.Command{ subcmdList = cli.Command{
Name: "list", Name: "list",
Usage: "List files matching the given pattern", Usage: "List files matching the given pattern",
Action: runList, Action: runList,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "include-vendored", Name: "include-vendored,vendor",
Aliases: []string{"vendor"}, Usage: "Include files under public/vendor as well",
Usage: "Include files under public/vendor as well",
}, },
}, },
} }
subcmdView = &cli.Command{ subcmdView = cli.Command{
Name: "view", Name: "view",
Usage: "View a file matching the given pattern", Usage: "View a file matching the given pattern",
Action: runView, Action: runView,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "include-vendored", Name: "include-vendored,vendor",
Aliases: []string{"vendor"}, Usage: "Include files under public/vendor as well",
Usage: "Include files under public/vendor as well",
}, },
}, },
} }
subcmdExtract = &cli.Command{ subcmdExtract = cli.Command{
Name: "extract", Name: "extract",
Usage: "Extract resources", Usage: "Extract resources",
Action: runExtract, Action: runExtract,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "include-vendored", Name: "include-vendored,vendor",
Aliases: []string{"vendor"}, Usage: "Include files under public/vendor as well",
Usage: "Include files under public/vendor as well",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "overwrite", Name: "overwrite",
Usage: "Overwrite files if they already exist", Usage: "Overwrite files if they already exist",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "rename", Name: "rename",
Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)", Usage: "Rename files as {name}.bak if they already exist (overwrites previous .bak)",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "custom", Name: "custom",
Usage: "Extract to the 'custom' directory as per app.ini", Usage: "Extract to the 'custom' directory as per app.ini",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "destination", Name: "destination,dest-dir",
Aliases: []string{"dest-dir"}, Usage: "Extract to the specified directory",
Usage: "Extract to the specified directory",
}, },
}, },
} }
@ -101,10 +96,10 @@ 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())
if err != nil { if err != nil {
return err return err
} }
@ -116,31 +111,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,15 +147,15 @@ 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
} }
if len(matchedAssetFiles) == 0 { if len(matchedAssetFiles) == 0 {
return errors.New("no files matched the given pattern") return fmt.Errorf("no files matched the given pattern")
} else if len(matchedAssetFiles) > 1 { } else if len(matchedAssetFiles) > 1 {
return errors.New("too many files matched the given pattern, try to be more specific") return fmt.Errorf("too many files matched the given pattern, try to be more specific")
} }
data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name) data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name)
@ -175,13 +170,13 @@ 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
} }
if c.NArg() == 0 { if len(c.Args()) == 0 {
return errors.New("a list of pattern of files to extract is mandatory (e.g. '**' for all)") return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
} }
destdir := "." destdir := "."
@ -217,7 +212,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 +267,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 +290,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,57 +5,56 @@
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"
) )
var ( var (
// CmdGenerate represents the available generate sub-command. // CmdGenerate represents the available generate sub-command.
CmdGenerate = &cli.Command{ CmdGenerate = cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate Gitea's secrets/keys/tokens", Usage: "Command line interface for running generators",
Commands: []*cli.Command{ Subcommands: []cli.Command{
subcmdSecret, subcmdSecret,
}, },
} }
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,
}, },
} }
microcmdGenerateInternalToken = &cli.Command{ microcmdGenerateInternalToken = cli.Command{
Name: "INTERNAL_TOKEN", Name: "INTERNAL_TOKEN",
Usage: "Generate a new INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN",
Action: runGenerateInternalToken, Action: runGenerateInternalToken,
} }
microcmdGenerateLfsJwtSecret = &cli.Command{ microcmdGenerateLfsJwtSecret = cli.Command{
Name: "JWT_SECRET", Name: "JWT_SECRET",
Aliases: []string{"LFS_JWT_SECRET"}, Aliases: []string{"LFS_JWT_SECRET"},
Usage: "Generate a new JWT_SECRET", Usage: "Generate a new JWT_SECRET",
Action: runGenerateLfsJwtSecret, Action: runGenerateLfsJwtSecret,
} }
microcmdGenerateSecretKey = &cli.Command{ microcmdGenerateSecretKey = cli.Command{
Name: "SECRET_KEY", Name: "SECRET_KEY",
Usage: "Generate a new SECRET_KEY", Usage: "Generate a new SECRET_KEY",
Action: runGenerateSecretKey, Action: runGenerateSecretKey,
} }
) )
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,13 +69,13 @@ 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.NewJwtSecretBase64()
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("%s", jwtSecretBase64) fmt.Printf("%s", JWTSecretBase64)
if isatty.IsTerminal(os.Stdout.Fd()) { if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Printf("\n") fmt.Printf("\n")
@ -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,21 +20,21 @@ 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"
) )
const ( const (
hookBatchSize = 500 hookBatchSize = 30
) )
var ( var (
// CmdHook represents the available hooks sub-command. // CmdHook represents the available hooks sub-command.
CmdHook = &cli.Command{ CmdHook = cli.Command{
Name: "hook", Name: "hook",
Usage: "(internal) Should only be called by Git", Usage: "Delegate commands to corresponding Git hooks",
Description: "Delegate commands to corresponding Git hooks", Description: "This should only be called by Git",
Before: PrepareConsoleLoggerLevel(log.FATAL), Before: PrepareConsoleLoggerLevel(log.FATAL),
Commands: []*cli.Command{ Subcommands: []cli.Command{
subcmdHookPreReceive, subcmdHookPreReceive,
subcmdHookUpdate, subcmdHookUpdate,
subcmdHookPostReceive, subcmdHookPostReceive,
@ -42,47 +42,47 @@ var (
}, },
} }
subcmdHookPreReceive = &cli.Command{ subcmdHookPreReceive = cli.Command{
Name: "pre-receive", Name: "pre-receive",
Usage: "Delegate pre-receive Git hook", Usage: "Delegate pre-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
Action: runHookPreReceive, Action: runHookPreReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
subcmdHookUpdate = &cli.Command{ subcmdHookUpdate = cli.Command{
Name: "update", Name: "update",
Usage: "Delegate update Git hook", Usage: "Delegate update Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
Action: runHookUpdate, Action: runHookUpdate,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
subcmdHookPostReceive = &cli.Command{ subcmdHookPostReceive = cli.Command{
Name: "post-receive", Name: "post-receive",
Usage: "Delegate post-receive Git hook", Usage: "Delegate post-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
Action: runHookPostReceive, Action: runHookPostReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
} }
// Note: new hook since git 2.29 // Note: new hook since git 2.29
subcmdHookProcReceive = &cli.Command{ subcmdHookProcReceive = cli.Command{
Name: "proc-receive", Name: "proc-receive",
Usage: "Delegate proc-receive Git hook", Usage: "Delegate proc-receive Git hook",
Description: "This command should only be called by Git", Description: "This command should only be called by Git",
Action: runHookProcReceive, Action: runHookProcReceive,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
}, },
}, },
@ -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"))
@ -218,7 +220,10 @@ Gitea or set your environment appropriately.`, "")
} }
} }
supportProcReceive := git.DefaultFeatures().SupportProcReceive supportProcReceive := false
if git.CheckGitVersionAtLeast("2.29") == nil {
supportProcReceive = true
}
for scanner.Scan() { for scanner.Scan() {
// TODO: support news feeds for wiki // TODO: support news feeds for wiki
@ -288,31 +293,20 @@ Gitea or set your environment appropriately.`, "")
return nil return nil
} }
// runHookUpdate avoid to do heavy operations on update hook because it will be func runHookUpdate(c *cli.Context) error {
// invoked for every ref update which does not like pre-receive and post-receive
func runHookUpdate(_ context.Context, c *cli.Command) error {
if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
return nil
}
// Update is empty and is kept only for backwards compatibility // Update is empty and is kept only for backwards compatibility
if len(os.Args) < 3 {
return nil
}
refName := git.RefName(os.Args[len(os.Args)-3])
if refName.IsPull() {
// ignore update to refs/pull/xxx/head, so we don't need to output any information
os.Exit(1)
}
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
@ -347,7 +341,6 @@ Gitea or set your environment appropriately.`, "")
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
repoName := os.Getenv(repo_module.EnvRepoName) repoName := os.Getenv(repo_module.EnvRepoName)
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
pusherName := os.Getenv(repo_module.EnvPusherName) pusherName := os.Getenv(repo_module.EnvPusherName)
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
@ -357,8 +350,6 @@ Gitea or set your environment appropriately.`, "")
GitObjectDirectory: os.Getenv(private.GitObjectDirectory), GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
GitQuarantinePath: os.Getenv(private.GitQuarantinePath), GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
GitPushOptions: pushOptions(), GitPushOptions: pushOptions(),
PullRequestID: prID,
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
} }
oldCommitIDs := make([]string, hookBatchSize) oldCommitIDs := make([]string, hookBatchSize)
newCommitIDs := make([]string, hookBatchSize) newCommitIDs := make([]string, hookBatchSize)
@ -385,9 +376,7 @@ Gitea or set your environment appropriately.`, "")
oldCommitIDs[count] = string(fields[0]) oldCommitIDs[count] = string(fields[0])
newCommitIDs[count] = string(fields[1]) newCommitIDs[count] = string(fields[1])
refFullNames[count] = git.RefName(fields[2]) refFullNames[count] = git.RefName(fields[2])
if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
commitID, _ := git.NewIDFromString(newCommitIDs[count])
if refFullNames[count] == git.BranchPrefix+"master" && !commitID.IsZero() && count == total {
masterPushed = true masterPushed = true
} }
count++ count++
@ -457,30 +446,27 @@ Gitea or set your environment appropriately.`, "")
func hookPrintResults(results []private.HookPostReceiveBranchResult) { func hookPrintResults(results []private.HookPostReceiveBranchResult) {
for _, res := range results { for _, res := range results {
hookPrintResult(res.Message, res.Create, res.Branch, res.URL) if !res.Message {
} continue
} }
func hookPrintResult(output, isCreate bool, branch, url string) { fmt.Fprintln(os.Stderr, "")
if !output { if res.Create {
return fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", res.URL)
}
fmt.Fprintln(os.Stderr, "")
os.Stderr.Sync()
} }
fmt.Fprintln(os.Stderr, "")
if isCreate {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
fmt.Fprintf(os.Stderr, " %s\n", url)
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", url)
}
fmt.Fprintln(os.Stderr, "")
_ = os.Stderr.Sync()
} }
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 +477,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 {
@ -503,7 +492,7 @@ Gitea or set your environment appropriately.`, "")
return nil return nil
} }
if !git.DefaultFeatures().SupportProcReceive { if git.CheckGitVersionAtLeast("2.29") != nil {
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
} }
@ -534,14 +523,14 @@ Gitea or set your environment appropriately.`, "")
index := bytes.IndexByte(rs.Data, byte(0)) index := bytes.IndexByte(rs.Data, byte(0))
if index >= len(rs.Data) { if index >= len(rs.Data) {
return fail(ctx, "Protocol: format error", "pkt-line: format error %s", rs.Data) return fail(ctx, "Protocol: format error", "pkt-line: format error "+fmt.Sprint(rs.Data))
} }
if index < 0 { if index < 0 {
if len(rs.Data) == 10 && rs.Data[9] == '\n' { if len(rs.Data) == 10 && rs.Data[9] == '\n' {
index = 9 index = 9
} else { } else {
return fail(ctx, "Protocol: format error", "pkt-line: format error %s", rs.Data) return fail(ctx, "Protocol: format error", "pkt-line: format error "+fmt.Sprint(rs.Data))
} }
} }
@ -583,9 +572,8 @@ Gitea or set your environment appropriately.`, "")
// S: ... ... // S: ... ...
// S: flush-pkt // S: flush-pkt
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserName: pusherName, UserName: pusherName,
UserID: pusherID, UserID: pusherID,
GitPushOptions: make(map[string]string),
} }
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
@ -610,6 +598,8 @@ Gitea or set your environment appropriately.`, "")
hookOptions.RefFullNames = append(hookOptions.RefFullNames, git.RefName(t[2])) hookOptions.RefFullNames = append(hookOptions.RefFullNames, git.RefName(t[2]))
} }
hookOptions.GitPushOptions = make(map[string]string)
if hasPushOptions { if hasPushOptions {
for { for {
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
@ -620,7 +610,11 @@ Gitea or set your environment appropriately.`, "")
if rs.Type == pktLineTypeFlush { if rs.Type == pktLineTypeFlush {
break break
} }
hookOptions.GitPushOptions.AddFromKeyValue(string(rs.Data))
kv := strings.SplitN(string(rs.Data), "=", 2)
if len(kv) == 2 {
hookOptions.GitPushOptions[kv[0]] = kv[1]
}
} }
} }
@ -675,8 +669,7 @@ Gitea or set your environment appropriately.`, "")
if err != nil { if err != nil {
return err return err
} }
commitID, _ := git.NewIDFromString(rs.OldOID) if rs.OldOID != git.EmptySHA {
if !commitID.IsZero() {
err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID)) err = writeDataPktLine(ctx, os.Stdout, []byte("option old-oid "+rs.OldOID))
if err != nil { if err != nil {
return err return err
@ -695,12 +688,6 @@ Gitea or set your environment appropriately.`, "")
} }
err = writeFlushPktLine(ctx, os.Stdout) err = writeFlushPktLine(ctx, os.Stdout)
if err == nil {
for _, res := range resp.Results {
hookPrintResult(res.ShouldShowMessage, res.IsCreatePR, res.HeadBranch, res.URL)
}
}
return err return err
} }
@ -732,7 +719,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)

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