Compare commits

...
This repository has been archived on 2024-01-04. You can view files and clone it, but cannot push or open issues or pull requests.

54 Commits

Author SHA1 Message Date
Loïc Dachary 823ab34c64
[CI] set PASSWORD_HASH_ALGO = argon2 for integration tests
(cherry picked from commit 1d7ce2a39c)
(cherry picked from commit 1abfc0c0a1)
2023-03-05 23:05:53 +00:00
Loïc Dachary e91c7e1fb6
[CI] implementation: forgejo container images
(cherry picked from commit dd1971d4e6)
(cherry picked from commit 3981dbaf8c)
(cherry picked from commit b6ae4ba255)
2023-03-05 23:05:53 +00:00
Loïc Dachary adebc44671
[CI] implementation: Woodpecker based CI
(cherry picked from commit c2a7aaeee8)
(cherry picked from commit 4277bdb741)
2023-03-05 23:05:53 +00:00
Loïc Dachary 0915c367ce
[CI] implementation: publish forgejo- binaries instead of gitea-
(cherry picked from commit 6d910daafb)
(cherry picked from commit d447861cc9)
(cherry picked from commit 60071e20a9)
2023-03-05 23:05:53 +00:00
Giteabot e3b1ebbbfe
Scoped labels: set aria-disabled on muted Exclusive option for a11y (#23306) (#23311)
Backport #23306

It is convenient to be able to toggle off this option after removing /
from the name. This ensures the muted state is communicated to blind
users even when the input is not fully disabled.

Part of #22974

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-05 23:05:11 +08:00
Giteabot 17ae7e335e
Add basic documentation for labels, including scoped labels (#23304) (#23309)
Backport #23304

Part of #22974

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Co-authored-by: delvh <dev.lh@web.de>
2023-03-05 20:38:46 +08:00
Giteabot 1edb57eda9
Fix various bugs for "install" page (#23194) (#23286)
Backport #23194

## TLDR

* Fix the broken page / broken image problem when click "Install"
* Fix the Password Hash Algorithm display problem for #22942
* Close #20089
* Close #23183
* Close #23184

## Details

### The broken page / broken image problem when clicking on "Install"
(Redirect failed after install - #23184)

Before: when clicking on "install", all new requests will fail, because the
server has been restarted. Users just see a broken page with broken
images, sometimes the server is not ready but the user would have been
redirect to "/user/login" page, then the users see a new broken page
(connection refused or something wrong ...)


After: only check InstallLock=true for necessary handlers, and sleep for
a while before restarting the server, then the browser has enough time
to load the "post-install" page. And there is a script to check whether
"/user/login" is ready, the user will only be redirected to the login
page when the server is ready.

### During new instance setup fill 'Gitea Base URL' with
window.location.origin - #20089

If the "app_url" input contains `localhost` (the default value from
config), use current window's location href as the `app_url` (aka
ROOT_URL)


### Fix the Password Hash Algorithm display problem for "Provide the
ability to set password hash algorithm parameters #22942"

Before: the UI shows `pbkdf2$50000$50`

<details>


![image](https://user-images.githubusercontent.com/2114189/221917143-e1e54798-1698-4fee-a18d-00c48081fc39.png)

</details>

After: the UI shows `pbkdf2`

<details>


![image](https://user-images.githubusercontent.com/2114189/221916999-97a15be8-2ebb-4a01-bf93-dac18e354fcc.png)

</details>



### GET data: net::ERR_INVALID_URL #23183

Cause by empty `data:` in `<link rel="manifest"
href="data:{{.ManifestData}}">`

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-05 11:59:58 +01:00
Giteabot a2a9b0f977
Support sanitising the URL by removing extra slashes in the URL (#21333) (#23300)
Backport #21333

Changes in this PR :

Strips incoming request URL of additional slashes (/). For example an
input like

`https://git.data.coop//halfd/new-website.git` is translated to
`https://git.data.coop/halfd/new-website.git`

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

Fix #23242

Co-authored-by: Sandeep Bhat <sandyethadka@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <leon@kske.dev>
2023-03-05 02:14:12 -05:00
Giteabot ff96f804b6
update to mermaid v10 (#23178) (#23299)
Backport #23178

fix #23153

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <leon@kske.dev>
2023-03-05 02:13:50 -05:00
Giteabot a926994bfe
Re-add accidentally removed `hacking-on-gitea.zh-cn.md` (#23297) (#23305) 2023-03-04 20:09:58 -05:00
Giteabot 83903535e3
Fix code wrap for unbroken lines (#23268) (#23293)
Backport #23268

## The Problem

`overflow-wrap: break-word` doesn't work well for unbroken lines. Use
`overflow-wrap: anywhere` instead, and remove legacy alias `word-wrap`

## Before


![image](https://user-images.githubusercontent.com/2114189222743939-5f38d9e4-18d8-4ae0-8078-4b3a59195a30.png)

## After


![image](https://user-images.githubusercontent.com/2114189/222743833-0e0cfdbb-7b2e-420d-99f9-b1b45dde521a.png)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-03-04 20:55:12 +01:00
Giteabot 8142408d3a
Fill head commit to in payload when notifying push commits for mirroring (#23215) (#23292)
Backport #23215

Just like what has been done when pushing manually:

7a5af25592/services/repository/push.go (L225-L226)

Before:

<img width="448" alt="image"
src="https://user-images.githubusercontent.com/9418365/222100123-cd4839d1-2d4d-45f7-7a0-0cbc73162b44.png">

After:

<img width="448" alt="image"
src="https://user-images.githubusercontent.com/9418365/222100225-3c5bb65-7ab9-41e2-8e39-9d84c23c352d.png">

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-04 19:02:50 +01:00
Giteabot a4158d1904
Avoid panic caused by broken payload when creating commit status (#23216) (#23294)
Backport #23216

When creating commit status for Actons jobs, a payload with nil
`HeadCommit` will cause panic.

Reported at:
https://gitea.com/gitea/act_runner/issues/28#issuecomment-732166

Although the `HeadCommit` probably can not be nil after #23215,
`CreateCommitStatus` should protect itself, to avoid being broken in the
future.

In addition, it's enough to print error log instead of returning err
when `CreateCommitStatus` failed.

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: delvh <dev.lh@web.de>
2023-03-04 14:23:49 +00:00
Giteabot 781019216c
Fix GetFilesChangedBetween if the file name may be escaped (#23272) (#23279)
Backport #23272

The code for GetFilesChangedBetween uses `git diff --name-only
base..head` to get the names of files changed between base and head
however this forgets that git will escape certain values.

This PR simply switches to use `-z` which has the `NUL` character as the
separator.

Ref https://github.com/go-gitea/gitea/pull/22568#discussion_r1123138096

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-03-04 00:38:31 -05:00
Giteabot 1322cd7a58
Use correct README link to render the README (#23152) (#23264)
Backport #23152

`renderReadmeFile` needs `readmeTreelink` as parameter but gets
`treeLink`.
The values of them look like as following:
`treeLink`:  `/{OwnerName}/{RepoName}/src/branch/{BranchName}`
`readmeTreelink`:
`/{OwnerName}/{RepoName}/src/branch/{BranchName}/{ReadmeFileName}`

`path.Dir` in

8540fc45b1/routers/web/repo/view.go (L316)
should convert `readmeTreelink` into
`/{OwnerName}/{RepoName}/src/branch/{BranchName}` instead of the current
`/{OwnerName}/{RepoName}/src/branch`.

Fixes #23151

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
2023-03-04 00:38:11 -05:00
Giteabot 464bbd747e
Fix commit retrieval by tag (#21804) (#23266)
Backport #21804

It is not correct to return tag data when commit data is requested, so
remove the hacky code that overwrote parts of a commit with parts of a
tag.

This fixes commit retrieval by tag for both the latest commit in the UI
and the commit info on tag webhook events.

Fixes: https://github.com/go-gitea/gitea/issues/21687
Replaces: https://github.com/go-gitea/gitea/pull/21693

<img width="324" alt="Screenshot 2022-11-13 at 15 26 37"
src="https://user-images.githubusercontent.com/115237/201526975-736c6ea7-ad6a-467a-a823-9a63d6ecb718.png">

<img width="789" alt="image"
src="https://user-images.githubusercontent.com/115237/201526876-90a13ffc-1e5c-4d76-911b-f1ae51e8eaab.png">

Co-authored-by: silverwind <me@silverwind.io>
2023-03-03 13:42:46 -06:00
Giteabot 574182e1eb
Revert relative links to absolute links in mail templates (#23267) (#23269)
Backport #23267

Follow #21986 , fix regression.

The mail templates should always use `AppURL` (the full absolute URL)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-03-03 17:41:00 +00:00
Giteabot ef8209a953
Use async await to fix empty quote reply at first time (#23168) (#23256)
Backport #23168

The reason why quote reply is empty is when quote reply is clicked, it
triggers the click function on `.comment-form-reply` button, and when
the first time this function is triggered, easyMDE for the reply has not
yet initialized, so that click handler of `.quote-reply` button in
`repo-legacy.js` got an `undefined` as easyMDE, and the following lines
which put quoted reply into the easyMDE is not executed.
The workaround in this PR is to pass the replied content to
'.comment-form-reply' button if easyMDE is not yet initialized (quote
reply first clicked) and put the replied content into it the after
easyMDE is created.
Now quote reply on first click:


https://user-images.githubusercontent.com/17645053/221452823-fc699d50-1649-4af1-952e-f04fc8d2978e.mov

<br />


Update:
The above change is not appropriate as stated in the
[comment](https://github.com/go-gitea/gitea/pull/23168#issuecomment-1445562284)
Use await instead

Close #22075.
Close #23247.

Co-authored-by: HesterG <hestergong@gmail.com>
2023-03-02 16:36:21 -06:00
Giteabot 9309098eab
Fix switched citation format (#23250) (#23253)
Backport #23250

Due to switched input parameters, the citation texts for Bibtex and Apa
were switched.
This pull request fixes #23244

Co-authored-by: Blender Defender <contact.blenderdefender@gmail.com>
2023-03-02 14:05:10 -06:00
Giteabot 790a79b04c
Fix missed `.hide` class (#23208) (#23237)
Backport #23208

https://github.com/go-gitea/gitea/pull/22950 removed `hide` class, and
use `gt-hidden`
But there are some missed `hide`....

Co-authored-by: yp05327 <576951401@qq.com>
2023-03-02 11:45:42 -06:00
Giteabot f8a40dafb9
Allow `<video>` in MarkDown (#22892) (#23236)
Backport #22892

As you can imagine, for the Blender development process it is rather
nice to be able to include videos in issues, pull requests, etc.

This PR allows the `<video>` HTML tag to be used in MarkDown, with the
`src`, `autoplay`, and `controls` attributes.

## Help Needed

To have this fully functional, personally I feel the following things
are still missing, and would appreciate some help from the Gitea team.

### Styling

Some CSS is needed, but I couldn't figure out which of the LESS files
would work. I tried `web_src/less/markup/content.less` and
`web_src/less/_base.less`, but after running `make` the changes weren't
seen in the frontend.

This I would consider a minimal set of CSS rules to be applied:

```css
video {
  max-width: 100%;
  max-height: 100vh;
}
```

### Default Attributes

It would be fantastic if Gitea could add some default attributes to the
`<video>` tag. Basically `controls` should always be there, as there is
no point in disallowing scrolling through videos, looping them, etc.

### Integration with the attachments system

Another thing that could be added, but probably should be done in a
separate PR, is the integration with the attachments system. Dragging in
a video should attach it, then generate the appropriate MarkDown/HTML.

Co-authored-by: Sybren <122987084+drsybren@users.noreply.github.com>
2023-03-02 11:49:05 -05:00
Giteabot 9843a0b741
Close the temp file when dumping database to make the temp file can be deleted on Windows (#23249) (#23251)
Backport #23249

There was no `dbDump.Close()` before, Windows doesn't like to delete
opened files.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-03-02 11:47:07 -05:00
Giteabot 085a4debd5
Fix incorrect checkbox behaviors in the dashboard repolist's filter (#23147) (#23205)
Backport #23147

Co-author: yp05327 , this PR is based on yp05327's #22813.

The problems of the old DashboardRepoList / repolist.tmpl: 

* It mixes many different frameworks together
* It "just works", bug on bug
* It uses many anti-pattern of Vue

This PR:

* Fix bugs and close #22800
* Decouple the "checkbox" elements from Fomantic UI (only use CSS
styles)
* Simplify the HTML layout
* Simplify JS logic
* Make it easier to refactor the DashboardRepoList into a pure Vue
component in the future.

### Screenshots

#### Default

![image](https://user-images.githubusercontent.com/2114189/221355768-a3eb5b23-85b4-4e3d-b906-844d8b15539d.png)

####  Click "Archived" to make it checked

![image](https://user-images.githubusercontent.com/2114189/221355777-9a104ddf-52a7-4504-869a-43a73827d802.png)

####  Click "Archived" to make it intermediate

![image](https://user-images.githubusercontent.com/2114189/221355802-0f67a073-67ad-4e92-84a6-558c432103a5.png)

####  Click "Archived" to make it unchecked

![image](https://user-images.githubusercontent.com/2114189/221355810-acf1d9d8-ccce-47fe-a02e-70cf4e666331.png)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-02 15:12:33 +08:00
Giteabot 4c1e24864f
Order pull request conflict checking by recently updated, for each push (#23220) (#23225)
Backport #23220

When a change is pushed to the default branch and many pull requests are
open for that branch, conflict checking can take some time.

Previously it would go from oldest to newest pull request. Now
prioritize pull requests that are likely being actively worked on or
prepared for merging.

This only changes the order within one push to one repository, but the
change is trivial and can already be quite helpful for smaller Gitea
instances where a few repositories have most pull requests. A global
order would require deeper changes to queues.

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-03-02 13:12:41 +08:00
Giteabot 5d5f907e7f
Add loading yaml label template files (#22976) (#23232)
Backport #22976

Extract from #11669 and enhancement to #22585 to support exclusive
scoped labels in label templates

* Move label template functionality to label module
* Fix handling of color codes
* Add Advanced label template

Co-authored-by: Lauris BH <lauris@nix.lv>
2023-03-01 21:57:34 -05:00
Giteabot 39178b5756
Do not create commit graph for temporary repos (#23219) (#23229)
Backport #23219

When fetching remotes for conflict checking, skip unnecessary and
potentially slow writing of commit graphs.

In a test with the Blender repository, this reduces conflict checking
time for one pull request from about 2s to 0.1s.

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
2023-03-02 01:53:41 +02:00
Giteabot 3d8412dd51
Use the correct selector to hide the checkmark of selected labels on clear (#23224) (#23228)
Backport #23224

Regression of #10107
(https://github.com/go-gitea/gitea/pull/10107/files#diff-a15e36f2f9c13339f7fdd38bc2887db2ff2945cb8434464318ab9105fcc846bdR460)

Fix #22222


Before: the "clear" action couldn't remove these check marks.


![image](https://user-images.githubusercontent.com/2114189/222212998-c9f33459-b71d-4e80-8588-2935f3b7050c.png)


After: the "clear" action can remove these  check marks.


![image](https://user-images.githubusercontent.com/2114189/222213048-2be98ed0-cac0-4e27-b72c-1dd0ac2637d5.png)

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-03-01 15:59:36 -05:00
Yarden Shoham ff7057a46d
Change button text for commenting and closing an issue at the same time (#23135) (#23182)
Backport #23135

Close  #10468

Without SimpleMDE/EasyMDE, using Simple Textarea, the button text could
be changed when content changes.

After introducing SimpleMDE/EasyMDE, there is no code for updating the
button text.



![image](https://user-images.githubusercontent.com/2114189/221334034-8d556cd5-1136-4ba0-8faa-a65ffadd7fb7.png)

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-02-28 17:54:24 -05:00
wxiaoguang bb8ef28913
Fix Fomantic UI's `touchstart` fastclick, always use `click` for click events (#23065) (#23195)
Backport #23065

Using `touchstart` for `click` events is a black magic for mobile
browsers (Google: `fastclick`).

However, it causes many UX problems if the fastclick is used without
careful design.

Fomantic UI uses this fastclick for its `dimmer` and `dropdown`, it
makes mobile users feel strange when they "touch" the dropdown menu.


This PR uses a simple patch to fix that behavior. Then the Fomantic
dropdown only uses `click` for click events.

This PR is simple enough and won't cause hidden bugs even if the patch
doesn't work. In the future, if there are more patches for Fomantic UI,
the patches could be placed in a directory like
`web_src/fomantic/patches/001-fix-click-touchstart`, etc.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-28 17:53:45 -05:00
Giteabot 13918ad344
Pass `--global` when calling `git config --get`, for consistency with `git config --set` (#23157) (#23199)
Backport #23157

This arose out of #22451; it seems we are checking using non-global
settings to see if a config value is set, in order to decide whether to
call another global(-indeed) configuration command. This PR changes it
so that both the check and the set are for global configuration.

Co-authored-by: Philip Peterson <philip-peterson@users.noreply.github.com>
2023-02-28 17:53:15 -05:00
Giteabot 7528ce60e7
Make `gitea serv` respect git binary home (#23138) (#23197)
Backport #23138

Close #23137

The old code is too old (8-9 years ago)

Let's try to execute the git commands from git bin home directly.

The verb has been checked above, it could only be:
* git-upload-pack
* git-upload-archive
* git-receive-pack
* git-lfs-authenticate

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-02-28 16:46:23 -06:00
Yarden Shoham 6c6a7e7d97
Avoid too long names for actions (#23162) (#23190)
Backport #23162

The name of the job or step comes from the workflow file, while the name
of the runner comes from its registration. If the strings used for these
names are too long, they could cause db issues.

Co-authored-by: Jason Song <i@wolfogre.com>
2023-02-28 13:42:40 +01:00
Yarden Shoham 111c509287
Add InsecureSkipVerify to Minio Client for Storage (#23166) (#23177)
Backport #23166

Allows using Minio with untrusted certificates

Closes #23128

Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-02-28 00:29:17 +02:00
Yarden Shoham 9d7ef0ad63
Add word-break to sidebar-item-link (#23146) (#23180)
Backport #23146

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

![image](https://user-images.githubusercontent.com/18380374/221351117-1e4b8922-04ca-4717-8e3b-c338a61bc062.png)

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: delvh <leon@kske.dev>
2023-02-27 15:59:36 -05:00
Yarden Shoham 9aae54c81f
Remove useless comment in #23114 (#23173) (#23175)
Backport #23173

The `isAdmin` param is no longer used so the comment should be removed.

d27d36f2f4/routers/web/explore/repo.go (L36-L37)

Co-authored-by: Zettat123 <zettat123@gmail.com>
2023-02-27 22:43:15 +08:00
Yarden Shoham 1bc4ffc337
Return 404 instead of 403 if user can not access the repo (#23155) (#23158)
Backport #23155

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

Before:

![image](https://user-images.githubusercontent.com/18380374/221390802-2317c6bc-d163-4def-b68b-6bb297143fe2.png)

After:

![image](https://user-images.githubusercontent.com/18380374/221390823-87490351-39c3-4a40-b1d2-11fc5b85fa24.png)

Co-authored-by: yp05327 <576951401@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-26 20:20:44 +08:00
Yarden Shoham 27879bc45e
Fix DBConsistency checks on MSSQL (#23132) (#23134)
Backport #23132

Unfortunately xorm's `builder.Select(...).From(...)` does not escape the
table names. This is mostly not a problem but is a problem with the
`user` table.

This PR simply escapes the user table. No other uses of `From("user")`
where found in the codebase so I think this should be all that is
needed.

Fix #23064

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: zeripath <art27@cantab.net>
2023-02-25 23:25:58 +08:00
Yarden Shoham a3694b6989
Fix secrets overview page missing from docs sidebar (#23143) (#23145)
Backport #23143

There was a warning while building the docs: `Building sites … WARN
2023/02/25 08:56:37
"/workspace/gitea/docs/content/doc/secrets/overview.en-us.md:1:1":
duplicate menu entry with identifier "overview" in menu "sidebar"`.

### Before

![image](https://user-images.githubusercontent.com/20454870/221348741-55cef254-f2ac-4507-9a66-818b406c668f.png)

### After

![image](https://user-images.githubusercontent.com/20454870/221348757-42066303-e1b7-43fe-9c4f-e05182fbabdd.png)

Signed-off-by: Yarden Shoham <hrsi88@gmail.com>
2023-02-25 13:30:32 +02:00
Yarden Shoham 28625fba5b
Redirect to the commit page after applying patch (#23056) (#23127)
Backport #23056

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

Co-authored-by: yp05327 <576951401@qq.com>
2023-02-24 22:43:26 -05:00
Yarden Shoham 7c3196ceac
Avoid warning for system setting when start up (#23054) (#23116)
Backport #23054

Partially fix #23050

After #22294 merged, it always has a warning log like `cannot get
context cache` when starting up. This should not affect any real life
but it's annoying. This PR will fix the problem. That means when
starting up, getting the system settings will not try from the cache but
will read from the database directly.

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
2023-02-24 16:46:11 -05:00
Yarden Shoham 80c1264f4b
Show empty repos in Admin Repository Management page (#23114) (#23130)
Backport #23114

The **Admin Repository Management** page and the **Explore Repository**
page both use the `RenderRepoSearch` function. In this function, the
`OnlyShowRelevant` search option is `true` when querying repositories
for admin page.


edf98a2dc3/routers/web/explore/repo.go (L99-L115)

Refer to
[#19361](https://github.com/go-gitea/gitea/pull/19361/files#diff-8058dfb85557010e0592d586675ec62ce406af7068e6311f39c160deac37f149R497),
the repositories with `is_empty=true` will be hidden if
`OnlyShowRelevant` is `true`.

Administrators should be able to see all repositories. So
`OnlyShowRelevant` shouldn't be set to `true` .

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
2023-02-24 20:17:21 +00:00
silverwind f0340c28f1
Change style to improve whitespaces trimming inside inline markdown code (#23093) (#23120)
Backport #23093

Given mardown source
```
x ` a` y
x `a ` y
x ` a ` y
```

Render

<img width="1421" alt="2023-02-23 15 33 14"

src="https://user-images.githubusercontent.com/17645053/220844280-a304c788-ac79-4a26-a55a-0db00f2fb3f3.png">

Fixes #23080.

Co-authored-by: HesterG <hestergong@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 17:05:36 +00:00
Yarden Shoham 5beb29ad35
Fix height for sticky head on large screen on PR page (#23111) (#23123)
Backport #23111

Right now on the PR 'File Change' Tab, the file title header sticky to
the top on large screens has wrong height, resulting in wrong ui
behavior when scrolling down. This PR is to fix this.

Before:

<img width="964" alt="截屏2023-02-24 17 12 29"
src="https://user-images.githubusercontent.com/17645053/221140409-025c4a84-6bbe-4b5b-a13f-bd2b79063522.png">

After:
<img width="1430" alt="截屏2023-02-24 21 10 12"
src="https://user-images.githubusercontent.com/17645053/221186750-0344d652-4610-4a90-a4c0-7f6269f950d6.png">

Co-authored-by: HesterG <hestergong@gmail.com>
2023-02-24 14:47:48 +00:00
Yarden Shoham 27e307142b
Fix db.Find bug (#23115) (#23119)
Backport #23115

Caused by #20821 

Fix #23110
2023-02-24 14:05:36 +00:00
Yarden Shoham e02e752f68
Fix nil context in RenderMarkdownToHtml (#23092) (#23108)
Backport #23092

Fix #23082.

This bug is caused by a nil context in
https://github.com/go-gitea/gitea/issues/23082#issuecomment-1441276546 .

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 21:17:03 +08:00
Yarden Shoham 5ddf67a9c2
Make issue meta dropdown support Enter, confirm before reloading (#23014) (#23102)
Backport #23014

As the title. Label/assignee share the same code.

* Close #22607
* Close #20727

Also:

* partially fix for #21742, now the comment reaction and menu work with
keyboard.
* partially fix for #17705, in most cases the comment won't be lost.
* partially fix for #21539
* partially fix for #20347
* partially fix for #7329

### The `Enter` support

Before, if user presses Enter, the dropdown just disappears and nothing
happens or the window reloads.

After, Enter can be used to select/deselect labels, and press Esc to
hide the dropdown to update the labels (still no way to cancel ....
maybe you can do a Cmd+R or F5 to refresh the window to discard the
changes .....)


This is only a quick patch, the UX is still not perfect, but it's much
better than before.


### The `confirm` before reloading

And more fixes for the `reload` problem, the new behaviors:

* If nothing changes (just show/hide the dropdown), then the page won't
be reloaded.
* If there are draft comments, show a confirm dialog before reloading,
to avoid losing comments.

That's the best effect can be done at the moment, unless completely
refactor these dropdown related code.

Screenshot of the confirm dialog:

<details>


![image](https://user-images.githubusercontent.com/2114189/220538288-e2da8459-6a4e-43cb-8596-74057f8a03a2.png)

</details>

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 17:40:36 +08:00
Yarden Shoham 4d3e2b23b8
Fix SyncOnCommit always return false in API of push_mirrors (#23088) (#23100)
Backport #23088

Fix: #22990

---
Before, the return value of the api is always false,regrardless of
whether the entry of `sync_on_commit` is true or false.
I have confirmed that the value of `sync_on_commit` dropped into the
database is correct.
So, I think it is enough to make some small changes.

Co-authored-by: sillyguodong <33891828+sillyguodong@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 14:36:25 +08:00
Yarden Shoham ddf61373f6
Add wrapper to author to avoid long name ui problem (#23030) (#23098)
Backport #23030

This PR is a possible solution for issue #22866. Main change is to add a
`author-wrapper` class around author name, like the wrapper added to
message. The `max-width` is set to 200px on PC, and 100px on mobile
device for now. Which will work like below:

<img width="1183" alt="2023-02-21 11 57 53"
src="https://user-images.githubusercontent.com/17645053/220244146-3d47c512-33b6-4ed8-938e-de0a8bc26ffb.png">

<img width="417" alt="2023-02-21 11 58 43"
src="https://user-images.githubusercontent.com/17645053/220244154-1ea0476b-9d1c-473a-9917-d3216860f9a9.png">

And `title` is added to the wrapper like it did in message wrapper. So
the full author name will show on hover.

Co-authored-by: HesterG <hestergong@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 10:45:18 +08:00
Yarden Shoham b4ed3f07e4
Fix commit name in Apply Patch page (#23086) (#23099)
Backport #23086

Fixes
https://github.com/go-gitea/gitea/issues/22621#issuecomment-1439309200

Co-authored-by: yp05327 <576951401@qq.com>
2023-02-24 08:47:35 +08:00
HesterG ced94f2e0d
Add accessibility to the menu on the navbar (#23059) (#23095)
Backport #23059

This PR is trying to add accessibility to the menu as mentioned in
#23053 so the menu can be accessed using keyboard (A quick demo is added
below), with a reference to
[PR2612](https://github.com/go-gitea/gitea/pull/22612). The goal is to
make the menu accessible merely using keyboard like shown below. And
this PR might need confirmation from developers using screen readers.
2023-02-23 20:56:03 +08:00
Yarden Shoham aff432b197
Nest metadata in refactoring docs (#23087) (#23091)
Backport #23087

Whitespace was missing from refactoring docs metadata.

backport label applied so it is included in versioned docs.

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-02-23 03:39:30 -05:00
Yarden Shoham 0ac3be1482
Improve accessibility for issue comments (#22612) (#23083)
Backport #22612

### Preamble

Gitea is an extremely great and smart solution perfectly suitable for
smaller systems and self-hosted Git-powered setups. However, there is a
group of people who have indredible difficulties in using Gitea,
rendering it useless in many cases. Those people are blind developers
using [screen readers](https://en.wikipedia.org/wiki/Screen_reader).
Unfortunately, the frontend framework is super convoluted, and Go
templates don’t allow accessibility improvements in a straightforward
way. As a blind developer myself, I'm trying to start fixing smaller
accessibility quirks with intention to go deeper and eventually, alone
or not, make Gitea at least mostly accessible for screen reader users.

### What This Fix Does

My blind fellows and me navigate webpages not very similarly to how a
sighted person does it. For instance, we love semantic HTML markup like
headings, lists, tables etc. because our screen readers allow us to jump
by those landmarks with a single keypress.
Currently in Gitea issue comments are not marked up with headings. I'm
trying to fix this by adding an appropriate
[ARIA](https://www.w3.org/WAI/standards-guidelines/aria/) role for
comment header and also by enclosing the comment itself in a semantical
article element.

Co-authored-by: Andre Polykanine <ap@oire.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-02-23 16:32:15 +08:00
Yarden Shoham 75eaf99076
Wrap unless-check in docker manifests (#23079) (#23081)
Backport #23079

Should fix the following:
> failed to render template: Evaluation error: Helper 'unless' called
with wrong number of arguments, needed 2 but got 3

https://go.dev/play/p/h7bt7MWKTcv

Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-02-23 01:34:47 +01:00
wxiaoguang e67d60d336
Fix some more hidden problems (#23074) (#23075)
Backport #23074
2023-02-22 15:26:48 +00:00
160 changed files with 2403 additions and 756 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
# Emacs
*~
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a

View File

@ -0,0 +1,75 @@
platform: linux/amd64
when:
event: [ push, pull_request, manual ]
branch:
exclude: [ soft-fork/*/*, soft-fork/*/*/* ]
variables:
- &golang_image 'golang:1.20'
- &test_image 'codeberg.org/forgejo/test_env:main'
- &goproxy_override ''
- &goproxy_setup |-
if [ -n "$${GOPROXY_OVERRIDE:-}" ]; then
export GOPROXY="$${GOPROXY_OVERRIDE}";
echo "Using goproxy from goproxy_override \"$${GOPROXY}\"";
elif [ -n "$${GOPROXY_DEFAULT:-}" ]; then
export GOPROXY="$${GOPROXY_DEFAULT}";
echo "Using goproxy from goproxy_default (secret) not displaying";
else
export GOPROXY="https://proxy.golang.org,direct";
echo "No goproxy overrides or defaults given, using \"$${GOPROXY}\"";
fi
workspace:
base: /go
path: src/codeberg/gitea
pipeline:
deps-backend:
image: *golang_image
pull: true
environment:
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make deps-backend
security-check:
image: *golang_image
group: checks
pull: true
environment:
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make security-check
lint-backend:
image: *test_image
pull: true
group: checks
environment:
GOPROXY_OVERRIDE: *goproxy_override
TAGS: 'bindata sqlite sqlite_unlock_notify'
GOSUMDB: 'sum.golang.org'
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make lint-backend
checks-backend:
image: *test_image
group: checks
environment:
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make --always-make checks-backend

View File

@ -0,0 +1,141 @@
platform: linux/amd64
when:
event: [ push, pull_request, manual ]
branch:
exclude: [ soft-fork/*/*, soft-fork/*/*/* ]
depends_on:
- compliance
variables:
- &golang_image 'golang:1.20'
- &test_image 'codeberg.org/forgejo/test_env:main'
- &mysql_image 'mysql:8'
- &pgsql_image 'postgres:10'
- &goproxy_override ''
- &goproxy_setup |-
if [ -n "$${GOPROXY_OVERRIDE:-}" ]; then
export GOPROXY="$${GOPROXY_OVERRIDE}";
echo "Using goproxy from goproxy_override \"$${GOPROXY}\"";
elif [ -n "$${GOPROXY_DEFAULT:-}" ]; then
export GOPROXY="$${GOPROXY_DEFAULT}";
echo "Using goproxy from goproxy_default (secret) not displaying";
else
export GOPROXY="https://proxy.golang.org,direct";
echo "No goproxy overrides or defaults given, using \"$${GOPROXY}\"";
fi
services:
mysql8:
image: *mysql_image
pull: true
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: testgitea
pgsql:
image: *pgsql_image
pull: true
environment:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
workspace:
base: /go
path: src/codeberg/gitea
pipeline:
git-safe:
image: *golang_image
pull: true
commands:
- git config --add safe.directory '*'
deps-backend:
image: *golang_image
pull: true
environment:
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make deps-backend
tag-pre-condition:
image: *golang_image
pull: true
commands:
- git update-ref refs/heads/tag_test ${CI_COMMIT_SHA}
prepare-test-env:
image: *test_image
pull: true
commands:
- ./build/test-env-prepare.sh
build:
image: *test_image
environment:
GOSUMDB: sum.golang.org
TAGS: bindata sqlite sqlite_unlock_notify
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- su gitea -c './build/test-env-check.sh'
- su gitea -c 'make backend'
unit-test:
image: *test_image
environment:
TAGS: 'bindata sqlite sqlite_unlock_notify'
RACE_ENABLED: 'true'
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- github_read_token
- goproxy_default
commands:
- *goproxy_setup
- su gitea -c 'make unit-test-coverage test-check'
test-mysql8:
group: integration
image: *test_image
commands:
- *goproxy_setup
- su gitea -c 'timeout -s ABRT 50m make test-mysql8-migration test-mysql8'
environment:
TAGS: 'bindata'
RACE_ENABLED: 'true'
USE_REPO_TEST_DIR: '1'
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
test-pgsql:
group: integration
image: *test_image
commands:
- *goproxy_setup
- su gitea -c 'timeout -s ABRT 50m make test-pgsql-migration test-pgsql'
environment:
TAGS: 'bindata'
RACE_ENABLED: 'true'
USE_REPO_TEST_DIR: '1'
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
test-sqlite:
group: integration
image: *test_image
environment:
- USE_REPO_TEST_DIR=1
- GOPROXY=off
- TAGS=bindata gogit sqlite sqlite_unlock_notify
- TEST_TAGS=bindata gogit sqlite sqlite_unlock_notify
commands:
- su gitea -c 'timeout -s ABRT 120m make test-sqlite-migration test-sqlite'

View File

@ -24,7 +24,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.17
LABEL maintainer="maintainers@gitea.io"
LABEL maintainer="contact@forgejo.org"
EXPOSE 22 3000

View File

@ -24,7 +24,7 @@ RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
RUN go build contrib/environment-to-ini/environment-to-ini.go
FROM alpine:3.17
LABEL maintainer="maintainers@gitea.io"
LABEL maintainer="contact@forgejo.org"
EXPOSE 2222 3000

View File

@ -83,7 +83,7 @@ ifneq ($(DRONE_TAG),)
GITEA_VERSION ?= $(VERSION)
else
ifneq ($(DRONE_BRANCH),)
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
VERSION ?= $(shell echo $(DRONE_BRANCH) | sed -e 's|v\([0-9.][0-9.]*\)/.*|\1|')
else
VERSION ?= main
endif
@ -286,8 +286,7 @@ misspell-check:
.PHONY: vet
vet:
@echo "Running go vet..."
@GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet
@$(GO) vet -vettool=gitea-vet $(GO_PACKAGES)
@$(GO) vet $(GO_PACKAGES)
.PHONY: $(TAGS_EVIDENCE)
$(TAGS_EVIDENCE):
@ -751,7 +750,7 @@ $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: release
release: frontend generate release-windows release-linux release-darwin release-freebsd release-copy release-compress vendor release-sources release-docs release-check
release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
$(DIST_DIRS):
mkdir -p $(DIST_DIRS)
@ -768,7 +767,7 @@ endif
.PHONY: release-linux
release-linux: | $(DIST_DIRS)
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) .
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 forgejo-$(VERSION) .
ifeq ($(CI),true)
cp /build/* $(DIST)/binaries
endif
@ -805,8 +804,8 @@ release-sources: | $(DIST_DIRS)
# bsdtar needs a ^ to prevent matching subdirectories
$(eval EXCL := --exclude=$(shell tar --help | grep -q bsdtar && echo "^")./)
# use transform to a add a release-folder prefix; in bsdtar the transform parameter equivalent is -s
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./gitea-src-$(VERSION)/'" || echo "--transform 's|^./|gitea-src-$(VERSION)/|'"))
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/gitea-src-$(VERSION).tar.gz .
$(eval TRANSFORM := $(shell tar --help | grep -q bsdtar && echo "-s '/^./forgejo-src-$(VERSION)/'" || echo "--transform 's|^./|forgejo-src-$(VERSION)/|'"))
tar $(addprefix $(EXCL),$(TAR_EXCLUDES)) $(TRANSFORM) -czf $(DIST)/release/forgejo-src-$(VERSION).tar.gz .
rm -f $(STORED_VERSION_FILE)
.PHONY: release-docs
@ -859,6 +858,8 @@ fomantic:
cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
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.*

View File

@ -272,6 +272,7 @@ func runDump(ctx *cli.Context) error {
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)
}

View File

@ -11,6 +11,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
@ -290,17 +291,21 @@ func runServ(c *cli.Context) error {
return nil
}
// Special handle for Windows.
if setting.IsWindows {
verb = strings.Replace(verb, "-", " ", 1)
}
var gitcmd *exec.Cmd
verbs := strings.Split(verb, " ")
if len(verbs) == 2 {
gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath)
} else {
gitcmd = exec.CommandContext(ctx, verb, repoPath)
gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
if _, err := os.Stat(gitBinVerb); err != nil {
// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
verbFields := strings.SplitN(verb, "-", 2)
if len(verbFields) == 2 {
// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
}
}
if gitcmd == nil {
// by default, use the verb (it has been checked above by allowedCommands)
gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
}
process.SetSysProcAttribute(gitcmd)

View File

@ -1871,6 +1871,9 @@ ROUTER = console
;;
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
;MINIO_USE_SSL = false
;;
;; Minio skip SSL verification available when STORAGE_TYPE is `minio`
;MINIO_INSECURE_SKIP_VERIFY = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -2552,6 +2555,9 @@ ROUTER = console
;;
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
;MINIO_USE_SSL = false
;;
;; Minio skip SSL verification available when STORAGE_TYPE is `minio`
;MINIO_INSECURE_SKIP_VERIFY = false
;[proxy]
;; Enable the proxy, all requests to external via HTTP will be affected

View File

@ -1,6 +1,6 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
{{#if build.tags}}
{{#unless contains "-rc" build.tag}}
{{#unless (contains "-rc" build.tag)}}
tags:
{{#each build.tags}}
- {{this}}-rootless

View File

@ -1,6 +1,6 @@
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
{{#if build.tags}}
{{#unless contains "-rc" build.tag }}
{{#unless (contains "-rc" build.tag)}}
tags:
{{#each build.tags}}
- {{this}}

View File

@ -854,6 +854,7 @@ Default templates for project boards:
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio`
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
## Log (`log`)
@ -1268,6 +1269,7 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
## Storage (`storage`)
@ -1280,6 +1282,7 @@ Default storage configuration for attachments, lfs, avatars and etc.
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
And you can also define a customize storage like below:
@ -1298,6 +1301,8 @@ MINIO_BUCKET = gitea
MINIO_LOCATION = us-east-1
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
MINIO_USE_SSL = false
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
MINIO_INSECURE_SKIP_VERIFY = false
```
And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`.
@ -1318,6 +1323,7 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
## Proxy (`proxy`)

View File

@ -431,6 +431,8 @@ MINIO_BUCKET = gitea
MINIO_LOCATION = us-east-1
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
MINIO_USE_SSL = false
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
MINIO_INSECURE_SKIP_VERIFY = false
```
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。

View File

@ -6,11 +6,11 @@ weight: 20
toc: false
draft: false
menu:
sidebar:
parent: "developers"
name: "Guidelines for Refactoring"
weight: 20
identifier: "guidelines-refactoring"
sidebar:
parent: "developers"
name: "Guidelines for Refactoring"
weight: 20
identifier: "guidelines-refactoring"
---
# Guidelines for Refactoring

View File

@ -0,0 +1,43 @@
---
date: "2016-12-01T16:00:00+02:00"
title: "加入 Gitea 开源"
slug: "hacking-on-gitea"
weight: 10
toc: false
draft: false
menu:
sidebar:
parent: "developers"
name: "加入 Gitea 开源"
weight: 10
identifier: "hacking-on-gitea"
---
# Hacking on Gitea
首先你需要一些运行环境,这和 [从源代码安装]({{< relref "doc/installation/from-source.zh-cn.md" >}}) 相同,如果你还没有设置好,可以先阅读那个章节。
如果你想为 Gitea 贡献代码,你需要 Fork 这个项目并且以 `master` 为开发分支。Gitea 使用 Govendor
来管理依赖,因此所有依赖项都被工具自动 copy 在 vendor 子目录下。用下面的命令来下载源码:
```
go get -d code.gitea.io/gitea
```
然后你可以在 Github 上 fork [Gitea 项目](https://github.com/go-gitea/gitea),之后可以通过下面的命令进入源码目录:
```
cd $GOPATH/src/code.gitea.io/gitea
```
要创建 pull requests 你还需要在源码中新增一个 remote 指向你 Fork 的地址,直接推送到 origin 的话会告诉你没有写权限:
```
git remote rename origin upstream
git remote add origin git@github.com:<USERNAME>/gitea.git
git fetch --all --prune
```
然后你就可以开始开发了。你可以看一下 `Makefile` 的内容。`make test` 可以运行测试程序, `make build` 将生成一个 `gitea` 可运行文件在根目录。如果你的提交比较复杂,尽量多写一些单元测试代码。
好了,到这里你已经设置好了所有的开发 Gitea 所需的环境。欢迎成为 Gitea 的 Contributor。

View File

@ -9,7 +9,7 @@ menu:
parent: "packages"
name: "Overview"
weight: 1
identifier: "overview"
identifier: "packages-overview"
---
# Package Registry

View File

@ -9,7 +9,7 @@ menu:
parent: "secrets"
name: "Overview"
weight: 1
identifier: "overview"
identifier: "secrets-overview"
---
# Secrets

View File

@ -0,0 +1,42 @@
---
date: "2023-03-04T19:00:00+00:00"
title: "Usage: Labels"
slug: "labels"
weight: 13
toc: false
draft: false
menu:
sidebar:
parent: "usage"
name: "Labels"
weight: 13
identifier: "labels"
---
# Labels
You can use labels to classify issues and pull requests and to improve your overview over them.
## Creating Labels
For repositories, labels can be created by going to `Issues` and clicking on `Labels`.
For organizations, you can define organization-wide labels that are shared with all organization repositories, including both already-existing repositories as well as newly created ones. Organization-wide labels can be created in the organization `Settings`.
Labels have a mandatory name, a mandatory color, an optional description, and must either be exclusive or not (see `Scoped labels` below).
When you create a repository, you can ensure certain labels exist by using the `Issue Labels` option. This option lists a number of available label sets that are [configured globally on your instance](../customizing-gitea/#labels). Its contained labels will all be created as well while creating the repository.
## Scoped Labels
A scoped label is a label that contains `/` in its name (not at either end of the name). For example labels `kind/bug` and `kind/enhancement` both have scope `kind`. Such labels will display the scope with slightly darker color.
The scope of a label is determined based on the **last** `/`, so for example the scope of label `scope/subscope/item` is `scope/subscope`.
Scoped labels can be marked as exclusive. This ensures at most a single label with the same scope is assigned to an issue or pull request. For example, if `kind/bug` and `kind/enhancement` are marked exclusive, an issue can only be classified as a bug or an enhancement.
## Filtering by Label
Issue and pull request lists can be filtered by label. Selecting multiple labels shows issues and pull requests that have all selected labels assigned.
By holding alt to click the label, issues and pull requests with the chosen label are excluded from the list.

View File

@ -194,6 +194,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
if len(needs) > 0 {
status = StatusBlocked
}
job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,

View File

@ -298,8 +298,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if len(workflowJob.Steps) > 0 {
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
for i, v := range workflowJob.Steps {
name, _ := util.SplitStringAtByteN(v.String(), 255)
steps[i] = &ActionTaskStep{
Name: v.String(),
Name: name,
TaskID: task.ID,
Index: int64(i),
RepoID: task.RepoID,

View File

@ -153,7 +153,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
return DefaultAvatarLink()
}
enableFederatedAvatar := system_model.GetSettingBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
var err error
if enableFederatedAvatar && system_model.LibravatarService != nil {
@ -174,7 +174,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
return urlStr
}
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
if !disableGravatar {
// copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *system_model.GravatarSourceURL

View File

@ -28,7 +28,7 @@ func enableGravatar(t *testing.T) {
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
assert.NoError(t, err)
setting.GravatarSource = gravatarSource
err = system_model.Init()
err = system_model.Init(db.DefaultContext)
assert.NoError(t, err)
}

View File

@ -134,7 +134,7 @@ func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake())
}
return sess.Find(&objects)
return sess.Find(objects)
}
// Count represents a common count function which accept an options interface
@ -148,5 +148,5 @@ func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (i
if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake())
}
return sess.FindAndCount(&objects)
return sess.FindAndCount(objects)
}

48
models/db/list_test.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db_test
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
"xorm.io/builder"
)
type mockListOptions struct {
db.ListOptions
}
func (opts *mockListOptions) IsListAll() bool {
return true
}
func (opts *mockListOptions) ToConds() builder.Cond {
return builder.NewCond()
}
func TestFind(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
xe := unittest.GetXORMEngine()
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
opts := mockListOptions{}
var repoUnits []repo_model.RepoUnit
err := db.Find(db.DefaultContext, &opts, &repoUnits)
assert.NoError(t, err)
assert.EqualValues(t, 83, len(repoUnits))
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
assert.NoError(t, err)
assert.EqualValues(t, 83, cnt)
repoUnits = make([]repo_model.RepoUnit, 0, 10)
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
assert.NoError(t, err)
assert.EqualValues(t, cnt, newCnt)
}

View File

@ -25,7 +25,7 @@
fork_id: 0
is_template: false
template_id: 0
size: 6708
size: 7028
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false

View File

@ -7,12 +7,12 @@ package issues
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error {
return util.ErrNotExist
}
// LabelColorPattern is a regexp witch can validate LabelColor
var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
// Label represents a label of repository for issues.
type Label struct {
ID int64 `xorm:"pk autoincr"`
@ -109,12 +106,12 @@ func init() {
}
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
func (label *Label) CalOpenIssues() {
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
func (l *Label) CalOpenIssues() {
l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
}
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
RepoID: repoID,
LabelIDs: []int64{labelID},
@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64)
})
for _, count := range counts {
label.NumOpenRepoIssues += count
l.NumOpenRepoIssues += count
}
}
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
var labelQuerySlice []string
labelSelected := false
labelID := strconv.FormatInt(label.ID, 10)
labelScope := label.ExclusiveScope()
labelID := strconv.FormatInt(l.ID, 10)
labelScope := l.ExclusiveScope()
for i, s := range currentSelectedLabels {
if s == label.ID {
if s == l.ID {
labelSelected = true
} else if -s == label.ID {
} else if -s == l.ID {
labelSelected = true
label.IsExcluded = true
l.IsExcluded = true
} else if s != 0 {
// Exclude other labels in the same scope from selection
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64,
if !labelSelected {
labelQuerySlice = append(labelQuerySlice, labelID)
}
label.IsSelected = labelSelected
label.QueryString = strings.Join(labelQuerySlice, ",")
l.IsSelected = labelSelected
l.QueryString = strings.Join(labelQuerySlice, ",")
}
// BelongsToOrg returns true if label is an organization label
func (label *Label) BelongsToOrg() bool {
return label.OrgID > 0
func (l *Label) BelongsToOrg() bool {
return l.OrgID > 0
}
// BelongsToRepo returns true if label is a repository label
func (label *Label) BelongsToRepo() bool {
return label.RepoID > 0
func (l *Label) BelongsToRepo() bool {
return l.RepoID > 0
}
// Get color as RGB values in 0..255 range
func (label *Label) ColorRGB() (float64, float64, float64, error) {
color, err := strconv.ParseUint(label.Color[1:], 16, 64)
func (l *Label) ColorRGB() (float64, float64, float64, error) {
color, err := strconv.ParseUint(l.Color[1:], 16, 64)
if err != nil {
return 0, 0, 0, err
}
@ -176,9 +173,9 @@ func (label *Label) ColorRGB() (float64, float64, float64, error) {
}
// Determine if label text should be light or dark to be readable on background color
func (label *Label) UseLightTextColor() bool {
if strings.HasPrefix(label.Color, "#") {
if r, g, b, err := label.ColorRGB(); err == nil {
func (l *Label) UseLightTextColor() bool {
if strings.HasPrefix(l.Color, "#") {
if r, g, b, err := l.ColorRGB(); err == nil {
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
// In the future WCAG 3 APCA may be a better solution
brightness := (0.299*r + 0.587*g + 0.114*b) / 255
@ -190,40 +187,26 @@ func (label *Label) UseLightTextColor() bool {
}
// Return scope substring of label name, or empty string if none exists
func (label *Label) ExclusiveScope() string {
if !label.Exclusive {
func (l *Label) ExclusiveScope() string {
if !l.Exclusive {
return ""
}
lastIndex := strings.LastIndex(label.Name, "/")
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 {
lastIndex := strings.LastIndex(l.Name, "/")
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {
return ""
}
return label.Name[:lastIndex]
return l.Name[:lastIndex]
}
// NewLabel creates a new label
func NewLabel(ctx context.Context, label *Label) error {
if !LabelColorPattern.MatchString(label.Color) {
return fmt.Errorf("bad color code: %s", label.Color)
func NewLabel(ctx context.Context, l *Label) error {
color, err := label.NormalizeColor(l.Color)
if err != nil {
return err
}
l.Color = color
// normalize case
label.Color = strings.ToLower(label.Color)
// add leading hash
if label.Color[0] != '#' {
label.Color = "#" + label.Color
}
// convert 3-character shorthand into 6-character version
if len(label.Color) == 4 {
r := label.Color[1]
g := label.Color[2]
b := label.Color[3]
label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
}
return db.Insert(ctx, label)
return db.Insert(ctx, l)
}
// NewLabels creates new labels
@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error {
}
defer committer.Close()
for _, label := range labels {
if !LabelColorPattern.MatchString(label.Color) {
return fmt.Errorf("bad color code: %s", label.Color)
for _, l := range labels {
color, err := label.NormalizeColor(l.Color)
if err != nil {
return err
}
if err := db.Insert(ctx, label); err != nil {
l.Color = color
if err := db.Insert(ctx, l); err != nil {
return err
}
}
@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error {
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
if !LabelColorPattern.MatchString(l.Color) {
return fmt.Errorf("bad color code: %s", l.Color)
color, err := label.NormalizeColor(l.Color)
if err != nil {
return err
}
l.Color = color
return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
}
// DeleteLabel delete a label
func DeleteLabel(id, labelID int64) error {
label, err := GetLabelByID(db.DefaultContext, labelID)
l, err := GetLabelByID(db.DefaultContext, labelID)
if err != nil {
if IsErrLabelNotExist(err) {
return nil
@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error {
sess := db.GetEngine(ctx)
if label.BelongsToOrg() && label.OrgID != id {
if l.BelongsToOrg() && l.OrgID != id {
return nil
}
if label.BelongsToRepo() && label.RepoID != id {
if l.BelongsToRepo() && l.RepoID != id {
return nil
}
@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
if err = issue.LoadRepo(ctx); err != nil {
return err
}
for _, label := range labels {
for _, l := range labels {
// Don't add already present labels and invalid labels
if HasIssueLabel(ctx, issue.ID, label.ID) ||
(label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) {
if HasIssueLabel(ctx, issue.ID, l.ID) ||
(l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) {
continue
}
if err = newIssueLabel(ctx, issue, label, doer); err != nil {
if err = newIssueLabel(ctx, issue, l, doer); err != nil {
return fmt.Errorf("newIssueLabel: %w", err)
}
}
@ -778,7 +767,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
norepo, err := db.GetEngine(ctx).Table("label").
Where(builder.And(
builder.Gt{"repo_id": 0},
builder.NotIn("repo_id", builder.Select("id").From("repository")),
builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
)).
Count()
if err != nil {
@ -788,7 +777,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
noorg, err := db.GetEngine(ctx).Table("label").
Where(builder.And(
builder.Gt{"org_id": 0},
builder.NotIn("org_id", builder.Select("id").From("user")),
builder.NotIn("org_id", builder.Select("id").From("`user`")),
)).
Count()
if err != nil {
@ -809,7 +798,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
if _, err := db.GetEngine(ctx).
Where(builder.And(
builder.Gt{"repo_id": 0},
builder.NotIn("repo_id", builder.Select("id").From("repository")),
builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
)).
Delete(Label{}); err != nil {
return err
@ -819,7 +808,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
if _, err := db.GetEngine(ctx).
Where(builder.And(
builder.Gt{"org_id": 0},
builder.NotIn("org_id", builder.Select("id").From("user")),
builder.NotIn("org_id", builder.Select("id").From("`user`")),
)).
Delete(Label{}); err != nil {
return err

View File

@ -15,8 +15,6 @@ import (
"github.com/stretchr/testify/assert"
)
// TODO TestGetLabelTemplateFile
func TestLabel_CalOpenIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})

View File

@ -111,6 +111,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ
return prs, db.GetEngine(db.DefaultContext).
Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
repoID, branch, false, false).
OrderBy("issue.updated_unix DESC").
Join("INNER", "issue", "issue.id=pull_request.issue_id").
Find(&prs)
}

View File

@ -39,9 +39,9 @@ import (
var ItemsPerPage = 40
// Init initialize model
func Init() error {
func Init(ctx context.Context) error {
unit.LoadUnitConfig()
return system_model.Init()
return system_model.Init(ctx)
}
// DeleteRepository deletes a repository for a user or organization.

View File

@ -79,8 +79,8 @@ func IsErrDataExpired(err error) bool {
return ok
}
// GetSettingNoCache returns specific setting without using the cache
func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
// GetSetting returns specific setting without using the cache
func GetSetting(ctx context.Context, key string) (*Setting, error) {
v, err := GetSettings(ctx, []string{key})
if err != nil {
return nil, err
@ -93,11 +93,11 @@ func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
const contextCacheKey = "system_setting"
// GetSetting returns the setting value via the key
func GetSetting(ctx context.Context, key string) (string, error) {
// GetSettingWithCache returns the setting value via the key
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
res, err := GetSettingNoCache(ctx, key)
res, err := GetSetting(ctx, key)
if err != nil {
return "", err
}
@ -110,6 +110,15 @@ func GetSetting(ctx context.Context, key string) (string, error) {
// none existing keys and errors are ignored and result in false
func GetSettingBool(ctx context.Context, key string) bool {
s, _ := GetSetting(ctx, key)
if s == nil {
return false
}
v, _ := strconv.ParseBool(s.SettingValue)
return v
}
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
s, _ := GetSettingWithCache(ctx, key)
v, _ := strconv.ParseBool(s)
return v
}
@ -120,7 +129,7 @@ func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error
keys[i] = strings.ToLower(keys[i])
}
settings := make([]*Setting, 0, len(keys))
if err := db.GetEngine(db.DefaultContext).
if err := db.GetEngine(ctx).
Where(builder.In("setting_key", keys)).
Find(&settings); err != nil {
return nil, err
@ -151,9 +160,9 @@ func (settings AllSettings) GetVersion(key string) int {
}
// GetAllSettings returns all settings from user
func GetAllSettings() (AllSettings, error) {
func GetAllSettings(ctx context.Context) (AllSettings, error) {
settings := make([]*Setting, 0, 5)
if err := db.GetEngine(db.DefaultContext).
if err := db.GetEngine(ctx).
Find(&settings); err != nil {
return nil, err
}
@ -168,12 +177,12 @@ func GetAllSettings() (AllSettings, error) {
func DeleteSetting(ctx context.Context, setting *Setting) error {
cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
cache.Remove(genSettingCacheKey(setting.SettingKey))
_, err := db.GetEngine(db.DefaultContext).Delete(setting)
_, err := db.GetEngine(ctx).Delete(setting)
return err
}
func SetSettingNoVersion(ctx context.Context, key, value string) error {
s, err := GetSettingNoCache(ctx, key)
s, err := GetSetting(ctx, key)
if IsErrSettingIsNotExist(err) {
return SetSetting(ctx, &Setting{
SettingKey: key,
@ -189,7 +198,7 @@ func SetSettingNoVersion(ctx context.Context, key, value string) error {
// SetSetting updates a users' setting for a specific key
func SetSetting(ctx context.Context, setting *Setting) error {
if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
return err
}
@ -205,8 +214,8 @@ func SetSetting(ctx context.Context, setting *Setting) error {
return nil
}
func upsertSettingValue(key, value string, version int) error {
return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
func upsertSettingValue(parentCtx context.Context, key, value string, version int) error {
return db.WithTx(parentCtx, func(ctx context.Context) error {
e := db.GetEngine(ctx)
// here we use a general method to do a safe upsert for different databases (and most transaction levels)
@ -249,9 +258,9 @@ var (
LibravatarService *libravatar.Libravatar
)
func Init() error {
func Init(ctx context.Context) error {
var disableGravatar bool
disableGravatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureDisableGravatar)
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
if IsErrSettingIsNotExist(err) {
disableGravatar = setting_module.GetDefaultDisableGravatar()
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
@ -262,7 +271,7 @@ func Init() error {
}
var enableFederatedAvatar bool
enableFederatedAvatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureEnableFederatedAvatar)
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
if IsErrSettingIsNotExist(err) {
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
@ -275,13 +284,13 @@ func Init() error {
if setting_module.OfflineMode {
disableGravatar = true
enableFederatedAvatar = false
if !GetSettingBool(db.DefaultContext, KeyPictureDisableGravatar) {
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureDisableGravatar, "true"); err != nil {
if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
}
}
if GetSettingBool(db.DefaultContext, KeyPictureEnableFederatedAvatar) {
if err := SetSettingNoVersion(db.DefaultContext, KeyPictureEnableFederatedAvatar, "false"); err != nil {
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
}
}

View File

@ -40,10 +40,10 @@ func TestSettings(t *testing.T) {
value, err := system.GetSetting(db.DefaultContext, keyName)
assert.NoError(t, err)
assert.EqualValues(t, updatedSetting.SettingValue, value)
assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue)
// get all settings
settings, err = system.GetAllSettings()
settings, err = system.GetAllSettings(db.DefaultContext)
assert.NoError(t, err)
assert.Len(t, settings, 3)
assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
@ -51,7 +51,7 @@ func TestSettings(t *testing.T) {
// delete setting
err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
assert.NoError(t, err)
settings, err = system.GetAllSettings()
settings, err = system.GetAllSettings(db.DefaultContext)
assert.NoError(t, err)
assert.Len(t, settings, 2)
}

View File

@ -113,7 +113,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
if err = storage.Init(); err != nil {
fatalTestError("storage.Init: %v\n", err)
}
if err = system_model.Init(); err != nil {
if err = system_model.Init(db.DefaultContext); err != nil {
fatalTestError("models.Init: %v\n", err)
}

View File

@ -67,7 +67,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
useLocalAvatar := false
autoGenerateAvatar := false
disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
switch {
case u.UseCustomAvatar:

View File

@ -41,9 +41,8 @@ var RecommendedHashAlgorithms = []string{
"pbkdf2_hi",
}
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and dealias it to
// a complete algorithm specification.
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
// hashAlgorithmToSpec converts an algorithm name or a specification to a full algorithm specification
func hashAlgorithmToSpec(algorithmName string) string {
if algorithmName == "" {
algorithmName = DefaultHashAlgorithmName
}
@ -52,10 +51,26 @@ func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHas
algorithmName = alias
alias, has = aliasAlgorithmNames[algorithmName]
}
// algorithmName should now be a full algorithm specification
// e.g. pbkdf2$50000$50 rather than pbdkf2
DefaultHashAlgorithm = Parse(algorithmName)
return algorithmName, DefaultHashAlgorithm
return algorithmName
}
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and de-alias it to
// a complete algorithm specification.
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
algoSpec := hashAlgorithmToSpec(algorithmName)
// now we get a full specification, e.g. pbkdf2$50000$50 rather than pbdkf2
DefaultHashAlgorithm = Parse(algoSpec)
return algoSpec, DefaultHashAlgorithm
}
// ConfigHashAlgorithm will try to find a "recommended algorithm name" defined by RecommendedHashAlgorithms for config
// This function is not fast and is only used for the installation page
func ConfigHashAlgorithm(algorithm string) string {
algorithm = hashAlgorithmToSpec(algorithm)
for _, recommAlgo := range RecommendedHashAlgorithms {
if algorithm == hashAlgorithmToSpec(recommAlgo) {
return recommAlgo
}
}
return algorithm
}

View File

@ -312,7 +312,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
}
func configSet(key, value string) error {
stdout, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err != nil && !err.IsExitCode(1) {
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
@ -331,7 +331,7 @@ func configSet(key, value string) error {
}
func configSetNonExist(key, value string) error {
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err == nil {
// already exist
return nil
@ -349,7 +349,7 @@ func configSetNonExist(key, value string) error {
}
func configAddNonExist(key, value string) error {
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
if err == nil {
// already exist
return nil
@ -366,7 +366,7 @@ func configAddNonExist(key, value string) error {
}
func configUnsetAll(key, value string) error {
_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
if err == nil {
// exist, need to remove
_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)

View File

@ -7,7 +7,6 @@
package git
import (
"fmt"
"strings"
"github.com/go-git/go-git/v5/plumbing"
@ -67,38 +66,6 @@ func (repo *Repository) IsCommitExist(name string) bool {
return err == nil
}
func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
if t.PGPSignature == "" {
return nil
}
var w strings.Builder
var err error
if _, err = fmt.Fprintf(&w,
"object %s\ntype %s\ntag %s\ntagger ",
t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
return nil
}
if err = t.Tagger.Encode(&w); err != nil {
return nil
}
if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
return nil
}
if _, err = fmt.Fprintf(&w, t.Message); err != nil {
return nil
}
return &CommitGPGSignature{
Signature: t.PGPSignature,
Payload: strings.TrimSpace(w.String()) + "\n",
}
}
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
var tagObject *object.Tag
@ -122,12 +89,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
commit := convertCommit(gogitCommit)
commit.repo = repo
if tagObject != nil {
commit.CommitMessage = strings.TrimSpace(tagObject.Message)
commit.Author = &tagObject.Tagger
commit.Signature = convertPGPSignatureForTag(tagObject)
}
tree, err := gogitCommit.Tree()
if err != nil {
return nil, err

View File

@ -107,10 +107,6 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
return nil, err
}
commit.CommitMessage = strings.TrimSpace(tag.Message)
commit.Author = tag.Tagger
commit.Signature = tag.Signature
return commit, nil
case "commit":
commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))

View File

@ -43,12 +43,13 @@ func TestGetTagCommitWithSignature(t *testing.T) {
assert.NoError(t, err)
defer bareRepo1.Close()
commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a")
// both the tag and the commit are signed here, this validates only the commit signature
commit, err := bareRepo1.GetCommit("28b55526e7100924d864dd89e35c1ea62e7a5a32")
assert.NoError(t, err)
assert.NotNil(t, commit)
assert.NotNil(t, commit.Signature)
// test that signature is not in message
assert.Equal(t, "tag", commit.CommitMessage)
assert.Equal(t, "signed-commit\n", commit.CommitMessage)
}
func TestGetCommitWithBadCommitID(t *testing.T) {

View File

@ -277,11 +277,18 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return strings.Split(stdout, "\n"), err
split := strings.Split(stdout, "\000")
// Because Git will always emit filenames with a terminal NUL ignore the last entry in the split - which will always be empty.
if len(split) > 0 {
split = split[:len(split)-1]
}
return split, err
}
// GetDiffFromMergeBase generates and return patch data from merge base to head

View File

@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) {
refs, err := bareRepo1.GetRefs()
assert.NoError(t, err)
assert.Len(t, refs, 5)
assert.Len(t, refs, 6)
expectedRefs := []string{
BranchPrefix + "branch1",
BranchPrefix + "branch2",
BranchPrefix + "master",
TagPrefix + "test",
TagPrefix + "signed-tag",
NotesRef,
}
@ -43,9 +44,12 @@ func TestRepository_GetRefsFiltered(t *testing.T) {
refs, err := bareRepo1.GetRefsFiltered(TagPrefix)
assert.NoError(t, err)
if assert.Len(t, refs, 1) {
assert.Equal(t, TagPrefix+"test", refs[0].Name)
if assert.Len(t, refs, 2) {
assert.Equal(t, TagPrefix+"signed-tag", refs[0].Name)
assert.Equal(t, "tag", refs[0].Type)
assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[0].Object.String())
assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", refs[0].Object.String())
assert.Equal(t, TagPrefix+"test", refs[1].Name)
assert.Equal(t, "tag", refs[1].Type)
assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[1].Object.String())
}
}

View File

@ -24,9 +24,9 @@ func TestRepository_GetCodeActivityStats(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, code)
assert.EqualValues(t, 9, code.CommitCount)
assert.EqualValues(t, 10, code.CommitCount)
assert.EqualValues(t, 3, code.AuthorCount)
assert.EqualValues(t, 9, code.CommitCountInAllBranches)
assert.EqualValues(t, 10, code.CommitCountInAllBranches)
assert.EqualValues(t, 10, code.Additions)
assert.EqualValues(t, 1, code.Deletions)
assert.Len(t, code.Authors, 3)

View File

@ -25,11 +25,14 @@ func TestRepository_GetTags(t *testing.T) {
assert.NoError(t, err)
return
}
assert.Len(t, tags, 1)
assert.Len(t, tags, 2)
assert.Equal(t, len(tags), total)
assert.EqualValues(t, "test", tags[0].Name)
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
assert.EqualValues(t, "signed-tag", tags[0].Name)
assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String())
assert.EqualValues(t, "tag", tags[0].Type)
assert.EqualValues(t, "test", tags[1].Name)
assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String())
assert.EqualValues(t, "tag", tags[1].Type)
}
func TestRepository_GetTag(t *testing.T) {

View File

@ -14,10 +14,10 @@ func TestGetLatestCommitTime(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path)
assert.NoError(t, err)
// Time is Sun Jul 21 22:43:13 2019 +0200
// Time is Sun Nov 13 16:40:14 2022 +0100
// which is the time of commit
// feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master)
assert.EqualValues(t, 1563741793, lct.Unix())
// ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master)
assert.EqualValues(t, 1668354014, lct.Unix())
}
func TestRepoIsEmpty(t *testing.T) {

Binary file not shown.

View File

@ -1 +1,2 @@
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200 push
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100 push

View File

@ -1 +1,2 @@
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200 push
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100 push

View File

@ -1 +1 @@
feaf4ba6bc635fec442f46ddd4512416ec43c2c2
ce064814f4a0d337b333e646ece456cd39fab612

View File

@ -0,0 +1 @@
36f97d9a96457e2bab511db30fe2db03893ebc64

46
modules/label/label.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"fmt"
"regexp"
"strings"
)
// colorPattern is a regexp which can validate label color
var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
// Label represents label information loaded from template
type Label struct {
Name string `yaml:"name"`
Color string `yaml:"color"`
Description string `yaml:"description,omitempty"`
Exclusive bool `yaml:"exclusive,omitempty"`
}
// NormalizeColor normalizes a color string to a 6-character hex code
func NormalizeColor(color string) (string, error) {
// normalize case
color = strings.TrimSpace(strings.ToLower(color))
// add leading hash
if len(color) == 6 || len(color) == 3 {
color = "#" + color
}
if !colorPattern.MatchString(color) {
return "", fmt.Errorf("bad color code: %s", color)
}
// convert 3-character shorthand into 6-character version
if len(color) == 4 {
r := color[1]
g := color[2]
b := color[3]
color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
}
return color, nil
}

126
modules/label/parser.go Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/options"
"gopkg.in/yaml.v3"
)
type labelFile struct {
Labels []*Label `yaml:"labels"`
}
// ErrTemplateLoad represents a "ErrTemplateLoad" kind of error.
type ErrTemplateLoad struct {
TemplateFile string
OriginalError error
}
// IsErrTemplateLoad checks if an error is a ErrTemplateLoad.
func IsErrTemplateLoad(err error) bool {
_, ok := err.(ErrTemplateLoad)
return ok
}
func (err ErrTemplateLoad) Error() string {
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
}
// GetTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description.
func GetTemplateFile(name string) ([]*Label, error) {
data, err := options.GetRepoInitFile("label", name+".yaml")
if err == nil && len(data) > 0 {
return parseYamlFormat(name+".yaml", data)
}
data, err = options.GetRepoInitFile("label", name+".yml")
if err == nil && len(data) > 0 {
return parseYamlFormat(name+".yml", data)
}
data, err = options.GetRepoInitFile("label", name)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
}
return parseLegacyFormat(name, data)
}
func parseYamlFormat(name string, data []byte) ([]*Label, error) {
lf := &labelFile{}
if err := yaml.Unmarshal(data, lf); err != nil {
return nil, err
}
// Validate label data and fix colors
for _, l := range lf.Labels {
l.Color = strings.TrimSpace(l.Color)
if len(l.Name) == 0 || len(l.Color) == 0 {
return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")}
}
color, err := NormalizeColor(l.Color)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)}
}
l.Color = color
}
return lf.Labels, nil
}
func parseLegacyFormat(name string, data []byte) ([]*Label, error) {
lines := strings.Split(string(data), "\n")
list := make([]*Label, 0, len(lines))
for i := 0; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if len(line) == 0 {
continue
}
parts, description, _ := strings.Cut(line, ";")
color, name, ok := strings.Cut(parts, " ")
if !ok {
return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
}
color, err := NormalizeColor(color)
if err != nil {
return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)}
}
list = append(list, &Label{
Name: strings.TrimSpace(name),
Color: color,
Description: strings.TrimSpace(description),
})
}
return list, nil
}
// LoadFormatted loads the labels' list of a template file as a string separated by comma
func LoadFormatted(name string) (string, error) {
var buf strings.Builder
list, err := GetTemplateFile(name)
if err != nil {
return "", err
}
for i := 0; i < len(list); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(list[i].Name)
}
return buf.String(), nil
}

View File

@ -0,0 +1,72 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package label
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestYamlParser(t *testing.T) {
data := []byte(`labels:
- name: priority/low
exclusive: true
color: "#0000ee"
description: "Low priority"
- name: priority/medium
exclusive: true
color: "0e0"
description: "Medium priority"
- name: priority/high
exclusive: true
color: "#ee0000"
description: "High priority"
- name: type/bug
color: "#f00"
description: "Bug"`)
labels, err := parseYamlFormat("test", data)
require.NoError(t, err)
require.Len(t, labels, 4)
assert.Equal(t, "priority/low", labels[0].Name)
assert.True(t, labels[0].Exclusive)
assert.Equal(t, "#0000ee", labels[0].Color)
assert.Equal(t, "Low priority", labels[0].Description)
assert.Equal(t, "priority/medium", labels[1].Name)
assert.True(t, labels[1].Exclusive)
assert.Equal(t, "#00ee00", labels[1].Color)
assert.Equal(t, "Medium priority", labels[1].Description)
assert.Equal(t, "priority/high", labels[2].Name)
assert.True(t, labels[2].Exclusive)
assert.Equal(t, "#ee0000", labels[2].Color)
assert.Equal(t, "High priority", labels[2].Description)
assert.Equal(t, "type/bug", labels[3].Name)
assert.False(t, labels[3].Exclusive)
assert.Equal(t, "#ff0000", labels[3].Color)
assert.Equal(t, "Bug", labels[3].Description)
}
func TestLegacyParser(t *testing.T) {
data := []byte(`#ee0701 bug ; Something is not working
#cccccc duplicate ; This issue or pull request already exists
#84b6eb enhancement`)
labels, err := parseLegacyFormat("test", data)
require.NoError(t, err)
require.Len(t, labels, 3)
assert.Equal(t, "bug", labels[0].Name)
assert.False(t, labels[0].Exclusive)
assert.Equal(t, "#ee0701", labels[0].Color)
assert.Equal(t, "Something is not working", labels[0].Description)
assert.Equal(t, "duplicate", labels[1].Name)
assert.False(t, labels[1].Exclusive)
assert.Equal(t, "#cccccc", labels[1].Color)
assert.Equal(t, "This issue or pull request already exists", labels[1].Description)
assert.Equal(t, "enhancement", labels[2].Name)
assert.False(t, labels[2].Exclusive)
assert.Equal(t, "#84b6eb", labels[2].Color)
assert.Empty(t, labels[2].Description)
}

View File

@ -132,6 +132,8 @@ func createDefaultPolicy() *bluemonday.Policy {
policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
policy.AllowAttrs("itemscope", "itemtype").OnElements("div")
// FIXME: Need to handle longdesc in img but there is no easy way to do it

44
modules/options/repo.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package options
import (
"fmt"
"os"
"path"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// GetRepoInitFile returns repository init files
func GetRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName)
// Use custom file when available.
customPath := path.Join(setting.CustomPath, relPath)
isFile, err := util.IsFile(customPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
}
if isFile {
return os.ReadFile(customPath)
}
switch tp {
case "readme":
return Readme(cleanedName)
case "gitignore":
return Gitignore(cleanedName)
case "license":
return License(cleanedName)
case "label":
return Labels(cleanedName)
default:
return []byte{}, fmt.Errorf("Invalid init file type")
}
}

View File

@ -106,7 +106,7 @@ func enableGravatar(t *testing.T) {
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
assert.NoError(t, err)
setting.GravatarSource = "https://secure.gravatar.com/avatar"
err = system_model.Init()
err = system_model.Init(db.DefaultContext)
assert.NoError(t, err)
}

View File

@ -23,6 +23,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@ -189,7 +190,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
// Check if label template exist
if len(opts.IssueLabels) > 0 {
if _, err := GetLabelTemplateFile(opts.IssueLabels); err != nil {
if _, err := label.GetTemplateFile(opts.IssueLabels); err != nil {
return nil, err
}
}

View File

@ -18,6 +18,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting"
@ -40,114 +41,6 @@ var (
LabelTemplates map[string]string
)
// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error.
type ErrIssueLabelTemplateLoad struct {
TemplateFile string
OriginalError error
}
// IsErrIssueLabelTemplateLoad checks if an error is a ErrIssueLabelTemplateLoad.
func IsErrIssueLabelTemplateLoad(err error) bool {
_, ok := err.(ErrIssueLabelTemplateLoad)
return ok
}
func (err ErrIssueLabelTemplateLoad) Error() string {
return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
}
// GetRepoInitFile returns repository init files
func GetRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName)
// Use custom file when available.
customPath := path.Join(setting.CustomPath, relPath)
isFile, err := util.IsFile(customPath)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
}
if isFile {
return os.ReadFile(customPath)
}
switch tp {
case "readme":
return options.Readme(cleanedName)
case "gitignore":
return options.Gitignore(cleanedName)
case "license":
return options.License(cleanedName)
case "label":
return options.Labels(cleanedName)
default:
return []byte{}, fmt.Errorf("Invalid init file type")
}
}
// GetLabelTemplateFile loads the label template file by given name,
// then parses and returns a list of name-color pairs and optionally description.
func GetLabelTemplateFile(name string) ([][3]string, error) {
data, err := GetRepoInitFile("label", name)
if err != nil {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
}
lines := strings.Split(string(data), "\n")
list := make([][3]string, 0, len(lines))
for i := 0; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, ";", 2)
fields := strings.SplitN(parts[0], " ", 2)
if len(fields) != 2 {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
}
color := strings.Trim(fields[0], " ")
if len(color) == 6 {
color = "#" + color
}
if !issues_model.LabelColorPattern.MatchString(color) {
return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)}
}
var description string
if len(parts) > 1 {
description = strings.TrimSpace(parts[1])
}
fields[1] = strings.TrimSpace(fields[1])
list = append(list, [3]string{fields[1], color, description})
}
return list, nil
}
func loadLabels(labelTemplate string) ([]string, error) {
list, err := GetLabelTemplateFile(labelTemplate)
if err != nil {
return nil, err
}
labels := make([]string, len(list))
for i := 0; i < len(list); i++ {
labels[i] = list[i][0]
}
return labels, nil
}
// LoadLabelsFormatted loads the labels' list of a template file as a string separated by comma
func LoadLabelsFormatted(labelTemplate string) (string, error) {
labels, err := loadLabels(labelTemplate)
return strings.Join(labels, ", "), err
}
// LoadRepoConfig loads the repository config
func LoadRepoConfig() {
// Load .gitignore and license files and readme templates.
@ -158,6 +51,14 @@ func LoadRepoConfig() {
if err != nil {
log.Fatal("Failed to get %s files: %v", t, err)
}
if t == "label" {
for i, f := range files {
ext := strings.ToLower(filepath.Ext(f))
if ext == ".yaml" || ext == ".yml" {
files[i] = f[:len(f)-len(ext)]
}
}
}
customPath := path.Join(setting.CustomPath, "options", t)
isDir, err := util.IsDir(customPath)
if err != nil {
@ -190,7 +91,7 @@ func LoadRepoConfig() {
// Load label templates
LabelTemplates = make(map[string]string)
for _, templateFile := range LabelTemplatesFiles {
labels, err := LoadLabelsFormatted(templateFile)
labels, err := label.LoadFormatted(templateFile)
if err != nil {
log.Error("Failed to load labels: %v", err)
}
@ -235,7 +136,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
}
// README
data, err := GetRepoInitFile("readme", opts.Readme)
data, err := options.GetRepoInitFile("readme", opts.Readme)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
}
@ -263,7 +164,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
var buf bytes.Buffer
names := strings.Split(opts.Gitignores, ",")
for _, name := range names {
data, err = GetRepoInitFile("gitignore", name)
data, err = options.GetRepoInitFile("gitignore", name)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
}
@ -281,7 +182,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
// LICENSE
if len(opts.License) > 0 {
data, err = GetRepoInitFile("license", opts.License)
data, err = options.GetRepoInitFile("license", opts.License)
if err != nil {
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err)
}
@ -443,7 +344,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
// InitializeLabels adds a label set to a repository using a template
func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error {
list, err := GetLabelTemplateFile(labelTemplate)
list, err := label.GetTemplateFile(labelTemplate)
if err != nil {
return err
}
@ -451,9 +352,10 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg
labels := make([]*issues_model.Label, len(list))
for i := 0; i < len(list); i++ {
labels[i] = &issues_model.Label{
Name: list[i][0],
Description: list[i][2],
Color: list[i][1],
Name: list[i].Name,
Exclusive: list[i].Exclusive,
Description: list[i].Description,
Color: list[i].Color,
}
if isOrg {
labels[i].OrgID = id

View File

@ -41,6 +41,7 @@ func getStorage(rootCfg ConfigProvider, name, typ string, targetSec *ini.Section
sec.Key("MINIO_BUCKET").MustString("gitea")
sec.Key("MINIO_LOCATION").MustString("us-east-1")
sec.Key("MINIO_USE_SSL").MustBool(false)
sec.Key("MINIO_INSECURE_SKIP_VERIFY").MustBool(false)
if targetSec == nil {
targetSec, _ = rootCfg.NewSection(name)

View File

@ -5,7 +5,9 @@ package storage
import (
"context"
"crypto/tls"
"io"
"net/http"
"net/url"
"os"
"path"
@ -42,13 +44,14 @@ const MinioStorageType Type = "minio"
// MinioStorageConfig represents the configuration for a minio storage
type MinioStorageConfig struct {
Endpoint string `ini:"MINIO_ENDPOINT"`
AccessKeyID string `ini:"MINIO_ACCESS_KEY_ID"`
SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY"`
Bucket string `ini:"MINIO_BUCKET"`
Location string `ini:"MINIO_LOCATION"`
BasePath string `ini:"MINIO_BASE_PATH"`
UseSSL bool `ini:"MINIO_USE_SSL"`
Endpoint string `ini:"MINIO_ENDPOINT"`
AccessKeyID string `ini:"MINIO_ACCESS_KEY_ID"`
SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY"`
Bucket string `ini:"MINIO_BUCKET"`
Location string `ini:"MINIO_LOCATION"`
BasePath string `ini:"MINIO_BASE_PATH"`
UseSSL bool `ini:"MINIO_USE_SSL"`
InsecureSkipVerify bool `ini:"MINIO_INSECURE_SKIP_VERIFY"`
}
// MinioStorage returns a minio bucket storage
@ -90,8 +93,9 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
minioClient, err := minio.New(config.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
Secure: config.UseSSL,
Creds: credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
Secure: config.UseSSL,
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
})
if err != nil {
return nil, convertMinioErr(err)

View File

@ -92,7 +92,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion
},
"DisableGravatar": func(ctx context.Context) bool {
return system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
return system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
},
"DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName
@ -174,8 +174,9 @@ func NewFuncMap() []template.FuncMap {
"RenderEmojiPlain": emoji.ReplaceAliases,
"ReactionToEmoji": ReactionToEmoji,
"RenderNote": RenderNote,
"RenderMarkdownToHtml": func(input string) template.HTML {
"RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML {
output, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: setting.AppSubURL,
}, input)
if err != nil {

View File

@ -0,0 +1,70 @@
labels:
- name: "Kind/Bug"
color: ee0701
description: Something is not working
- name: "Kind/Feature"
color: 0288d1
description: New functionality
- name: "Kind/Enhancement"
color: 84b6eb
description: Improve existing functionality
- name: "Kind/Security"
color: 9c27b0
description: This is security issue
- name: "Kind/Testing"
color: 795548
description: Issue or pull request related to testing
- name: "Kind/Breaking"
color: c62828
description: Breaking change that won't be backward compatible
- name: "Kind/Documentation"
color: 37474f
description: Documentation changes
- name: "Reviewed/Duplicate"
exclusive: true
color: 616161
description: This issue or pull request already exists
- name: "Reviewed/Invalid"
exclusive: true
color: 546e7a
description: Invalid issue
- name: "Reviewed/Confirmed"
exclusive: true
color: 795548
description: Issue has been confirmed
- name: "Reviewed/Won't Fix"
exclusive: true
color: eeeeee
description: This issue won't be fixed
- name: "Status/Need More Info"
exclusive: true
color: 424242
description: Feedback is required to reproduce issue or to continue work
- name: "Status/Blocked"
exclusive: true
color: 880e4f
description: Something is blocking this issue or pull request
- name: "Status/Abandoned"
exclusive: true
color: "222222"
description: Somebody has started to work on this but abandoned work
- name: "Priority/Critical"
exclusive: true
color: b71c1c
description: The priority is critical
priority: critical
- name: "Priority/High"
exclusive: true
color: d32f2f
description: The priority is high
priority: high
- name: "Priority/Medium"
exclusive: true
color: e64a19
description: The priority is medium
priority: medium
- name: "Priority/Low"
exclusive: true
color: 4caf50
description: The priority is low
priority: low

View File

@ -237,7 +237,6 @@ internal_token_failed = Failed to generate internal token: %v
secret_key_failed = Failed to generate secret key: %v
save_config_failed = Failed to save configuration: %v
invalid_admin_setting = Administrator account setting is invalid: %v
install_success = Welcome! Thank you for choosing Gitea. Have fun and take care!
invalid_log_root_path = The log path is invalid: %v
default_keep_email_private = Hide Email Addresses by Default
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
@ -248,6 +247,7 @@ default_enable_timetracking_popup = Enable time tracking for new repositories by
no_reply_address = Hidden Email Domain
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
password_algorithm = Password Hash Algorithm
invalid_password_algorithm = Invalid password hash algorithm
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. `argon2` whilst having good characteristics uses a lot of memory and may be inappropriate for small systems.
enable_update_checker = Enable Update Checker
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.

256
package-lock.json generated
View File

@ -31,7 +31,7 @@
"less": "4.1.3",
"less-loader": "11.1.0",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "9.3.0",
"mermaid": "10.0.2",
"mini-css-extract-plugin": "2.7.2",
"monaco-editor": "0.34.1",
"monaco-editor-webpack-plugin": "7.0.1",
@ -2680,6 +2680,14 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
"node_modules/cose-base": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
"integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
"dependencies": {
"layout-base": "^1.0.0"
}
},
"node_modules/cosmiconfig": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
@ -2888,6 +2896,53 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
},
"node_modules/cytoscape": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
"integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
"dependencies": {
"heap": "^0.2.6",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/cytoscape-cose-bilkent": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
"integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
"dependencies": {
"cose-base": "^1.0.0"
},
"peerDependencies": {
"cytoscape": "^3.2.0"
}
},
"node_modules/cytoscape-fcose": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
"integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
"dependencies": {
"cose-base": "^2.2.0"
},
"peerDependencies": {
"cytoscape": "^3.2.0"
}
},
"node_modules/cytoscape-fcose/node_modules/cose-base": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
"integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
"dependencies": {
"layout-base": "^2.0.0"
}
},
"node_modules/cytoscape-fcose/node_modules/layout-base": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
"integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
},
"node_modules/d3": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.8.2.tgz",
@ -3267,11 +3322,11 @@
}
},
"node_modules/dagre-d3-es": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz",
"integrity": "sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz",
"integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==",
"dependencies": {
"d3": "^7.7.0",
"d3": "^7.8.2",
"lodash-es": "^4.17.21"
}
},
@ -3298,6 +3353,11 @@
"node": ">=12"
}
},
"node_modules/dayjs": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -3632,9 +3692,9 @@
}
},
"node_modules/dompurify": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
},
"node_modules/domutils": {
"version": "3.0.1",
@ -3681,6 +3741,11 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
},
"node_modules/elkjs": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
"integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -5043,6 +5108,11 @@
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"node_modules/heap": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
"integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@ -5877,6 +5947,11 @@
"integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==",
"dev": true
},
"node_modules/layout-base": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
},
"node_modules/less": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@ -6049,8 +6124,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
@ -6393,20 +6467,26 @@
}
},
"node_modules/mermaid": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.3.0.tgz",
"integrity": "sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==",
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.0.2.tgz",
"integrity": "sha512-slwoB9WdNUT+/W9VhxLYRLZ0Ey12fIE+cAZjm3FmHTD+0F1uoJETfsNbVS1POnvQZhFYzfT6/z6hJZXgecqVBA==",
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0",
"dagre-d3-es": "7.0.6",
"dompurify": "2.4.1",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"dagre-d3-es": "7.0.9",
"dayjs": "^1.11.7",
"dompurify": "2.4.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"uuid": "^9.0.0"
"ts-dedent": "^2.2.0",
"uuid": "^9.0.0",
"web-worker": "^1.2.0"
}
},
"node_modules/micromatch": {
@ -6531,11 +6611,6 @@
"integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==",
"dev": true
},
"node_modules/moment-mini": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz",
"integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg=="
},
"node_modules/monaco-editor": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
@ -8807,6 +8882,14 @@
"node": ">=8"
}
},
"node_modules/ts-dedent": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
"engines": {
"node": ">=6.10"
}
},
"node_modules/tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@ -9315,6 +9398,11 @@
"node": ">=10.13.0"
}
},
"node_modules/web-worker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@ -11774,6 +11862,14 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
"cose-base": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
"integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
"requires": {
"layout-base": "^1.0.0"
}
},
"cosmiconfig": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
@ -11937,6 +12033,46 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
},
"cytoscape": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
"integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
"requires": {
"heap": "^0.2.6",
"lodash": "^4.17.21"
}
},
"cytoscape-cose-bilkent": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
"integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
"requires": {
"cose-base": "^1.0.0"
}
},
"cytoscape-fcose": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
"integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
"requires": {
"cose-base": "^2.2.0"
},
"dependencies": {
"cose-base": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
"integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
"requires": {
"layout-base": "^2.0.0"
}
},
"layout-base": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
"integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
}
}
},
"d3": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.8.2.tgz",
@ -12208,11 +12344,11 @@
}
},
"dagre-d3-es": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz",
"integrity": "sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz",
"integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==",
"requires": {
"d3": "^7.7.0",
"d3": "^7.8.2",
"lodash-es": "^4.17.21"
}
},
@ -12233,6 +12369,11 @@
"whatwg-url": "^11.0.0"
}
},
"dayjs": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -12472,9 +12613,9 @@
}
},
"dompurify": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
"integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
},
"domutils": {
"version": "3.0.1",
@ -12518,6 +12659,11 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
},
"elkjs": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
"integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -13548,6 +13694,11 @@
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
},
"heap": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
"integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
},
"hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@ -14129,6 +14280,11 @@
"integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==",
"dev": true
},
"layout-base": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
},
"less": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@ -14251,8 +14407,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.21",
@ -14531,20 +14686,26 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"mermaid": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.3.0.tgz",
"integrity": "sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==",
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.0.2.tgz",
"integrity": "sha512-slwoB9WdNUT+/W9VhxLYRLZ0Ey12fIE+cAZjm3FmHTD+0F1uoJETfsNbVS1POnvQZhFYzfT6/z6hJZXgecqVBA==",
"requires": {
"@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0",
"dagre-d3-es": "7.0.6",
"dompurify": "2.4.1",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"dagre-d3-es": "7.0.9",
"dayjs": "^1.11.7",
"dompurify": "2.4.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"uuid": "^9.0.0"
"ts-dedent": "^2.2.0",
"uuid": "^9.0.0",
"web-worker": "^1.2.0"
}
},
"micromatch": {
@ -14634,11 +14795,6 @@
}
}
},
"moment-mini": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz",
"integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg=="
},
"monaco-editor": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
@ -16340,6 +16496,11 @@
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
"dev": true
},
"ts-dedent": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="
},
"tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@ -16674,6 +16835,11 @@
"graceful-fs": "^4.1.2"
}
},
"web-worker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
},
"webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",

View File

@ -31,7 +31,7 @@
"less": "4.1.3",
"less-loader": "11.1.0",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "9.3.0",
"mermaid": "10.0.2",
"mini-css-extract-plugin": "2.7.2",
"monaco-editor": "0.34.1",
"monaco-editor-webpack-plugin": "7.0.1",

3
releases/Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM alpine:3.17
RUN echo root > state

View File

@ -0,0 +1,3 @@
FROM alpine:3.17
RUN echo rootless > state

View File

@ -0,0 +1,69 @@
#!/bin/sh
set -ex
test_teardown() {
setup_api
api DELETE repos/$PUSH_USER/forgejo/releases/tags/$TAG || true
api DELETE repos/$PUSH_USER/forgejo/tags/$TAG || true
rm -fr dist/release
setup_tea
$BIN_DIR/tea login delete $RELEASETEAMUSER || true
}
test_setup() {
mkdir -p $RELEASE_DIR
touch $RELEASE_DIR/file-one.txt
touch $RELEASE_DIR/file-two.txt
}
test_ensure_tag() {
api DELETE repos/$PUSH_USER/forgejo/tags/$TAG || true
#
# idempotent
#
ensure_tag
api GET repos/$PUSH_USER/forgejo/tags/$TAG > /tmp/tag1.json
ensure_tag
api GET repos/$PUSH_USER/forgejo/tags/$TAG > /tmp/tag2.json
diff -u /tmp/tag[12].json
#
# sanity check on the SHA of an existing tag
#
(
CI_COMMIT_SHA=12345
! ensure_tag
)
api DELETE repos/$PUSH_USER/forgejo/tags/$TAG
}
#
# Running the test locally instead of within Woodpecker
#
# 1. Setup: obtain a token at https://codeberg.org/user/settings/applications
# 2. Run: RELEASETEAMUSER=<username> RELEASETEAMTOKEn=<apptoken> binaries-pull-push-test.sh test_run
# 3. Verify: (optional) manual verification at https://codeberg.org/<username>/forgejo/releases
# 4. Cleanup: RELEASETEAMUSER=<username> RELEASETEAMTOKEn=<apptoken> binaries-pull-push-test.sh test_teardown
#
test_run() {
test_teardown
to_push=/tmp/binaries-releases-to-push
pulled=/tmp/binaries-releases-pulled
RELEASE_DIR=$to_push
test_setup
test_ensure_tag
echo "================================ TEST BEGIN"
push
RELEASE_DIR=$pulled
pull
diff -r $to_push $pulled
echo "================================ TEST END"
}
: ${CI_REPO_OWNER:=dachary}
: ${PULL_USER=$CI_REPO_OWNER}
: ${PUSH_USER=$CI_REPO_OWNER}
: ${CI_COMMIT_TAG:=W17.8.20-1}
: ${CI_COMMIT_SHA:=$(git rev-parse HEAD)}
. $(dirname $0)/binaries-pull-push.sh

88
releases/binaries-pull-push.sh Executable file
View File

@ -0,0 +1,88 @@
#!/bin/sh
set -ex
: ${PULL_USER:=forgejo-integration}
if test "$CI_REPO" = "forgejo/release" ; then
: ${PUSH_USER:=forgejo}
else
: ${PUSH_USER:=forgejo-experimental}
fi
: ${TAG:=${CI_COMMIT_TAG}}
: ${DOMAIN:=codeberg.org}
: ${RELEASE_DIR:=dist/release}
: ${BIN_DIR:=/tmp}
: ${TEA_VERSION:=0.9.0}
setup_tea() {
if ! test -f $BIN_DIR/tea ; then
curl -sL https://dl.gitea.io/tea/$TEA_VERSION/tea-$TEA_VERSION-linux-amd64 > $BIN_DIR/tea
chmod +x $BIN_DIR/tea
fi
}
ensure_tag() {
if api GET repos/$PUSH_USER/forgejo/tags/$TAG > /tmp/tag.json ; then
local sha=$(jq --raw-output .commit.sha < /tmp/tag.json)
if test "$sha" != "$CI_COMMIT_SHA" ; then
cat /tmp/tag.json
echo "the tag SHA in the $PUSH_USER repository does not match the tag SHA that triggered the build: $CI_COMMIT_SHA"
false
fi
else
api POST repos/$PUSH_USER/forgejo/tags --data-raw '{"tag_name": "'$CI_COMMIT_TAG'", "target": "'$CI_COMMIT_SHA'"}'
fi
}
upload() {
ASSETS=$(ls $RELEASE_DIR/* | sed -e 's/^/-a /')
echo "${CI_COMMIT_TAG}" | grep -qi '\-rc' && export RELEASETYPE="--prerelease" && echo "Uploading as Pre-Release"
echo "${CI_COMMIT_TAG}" | grep -qi '\-test' && export RELEASETYPE="--draft" && echo "Uploading as Draft"
test ${RELEASETYPE+false} || echo "Uploading as Stable"
ensure_tag
anchor=$(echo $CI_COMMIT_TAG | sed -e 's/^v//' -e 's/[^a-zA-Z0-9]/-/g')
$BIN_DIR/tea release create $ASSETS --repo $PUSH_USER/forgejo --note "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#${anchor}" --tag $CI_COMMIT_TAG --title $CI_COMMIT_TAG ${RELEASETYPE}
}
push() {
setup_api
setup_tea
GITEA_SERVER_TOKEN=$RELEASETEAMTOKEN $BIN_DIR/tea login add --name $RELEASETEAMUSER --url $DOMAIN
upload
}
setup_api() {
if ! which jq || ! which curl ; then
apk --update --no-cache add jq curl
fi
}
api() {
method=$1
shift
path=$1
shift
curl --fail -X $method -sS -H "Content-Type: application/json" -H "Authorization: token $RELEASETEAMTOKEN" "$@" https://$DOMAIN/api/v1/$path
}
pull() {
setup_api
(
mkdir -p $RELEASE_DIR
cd $RELEASE_DIR
api GET repos/$PULL_USER/forgejo/releases/tags/$TAG > /tmp/assets.json
jq --raw-output '.assets[] | "\(.name) \(.browser_download_url)"' < /tmp/assets.json | while read name url ; do
wget --quiet -O $name $url
done
)
}
missing() {
echo need pull or push argument got nothing
exit 1
}
${@:-missing}

View File

@ -0,0 +1,70 @@
#!/bin/sh
set -ex
image_delete() {
curl -sS -H @$TOKEN_HEADER -X DELETE https://$DOMAIN/v2/$1/forgejo/manifests/$2
}
#
# Create the same set of images that buildx would
#
test_setup() {
dir=$(dirname $0)
for suffix in '' '-rootless' ; do
(
cd $dir
manifests=""
for arch in $ARCHS ; do
image=$(arch_image_name $PULL_USER $arch $suffix)
docker build -f Dockerfile$suffix --platform linux/$arch -t $image .
docker push $image
images="$images $image"
done
manifest=$(image_name $PULL_USER $suffix)
docker manifest rm $manifest || true
docker manifest create $manifest $images
image_put $PULL_USER $(image_tag $suffix) $manifest
)
done
}
test_teardown() {
authenticate
for suffix in '' '-rootless' ; do
image_delete $PULL_USER $(image_tag $suffix)
image_delete $PUSH_USER $(image_tag $suffix)
image_delete $PUSH_USER $(short_image_tag $suffix)
for arch in $ARCHS ; do
image_delete $PULL_USER $(arch_image_tag $arch $suffix)
image_delete $PUSH_USER $(arch_image_tag $arch $suffix)
done
done
}
#
# Running the test locally instead of within Woodpecker
#
# 1. Setup: obtain a token at https://codeberg.org/user/settings/applications
# 2. Run: RELEASETEAMUSER=<username> RELEASETEAMTOKEn=<apptoken> container-images-pull-verify-push-test.sh test_run
# 3. Verify: (optional) manual verification at https://codeberg.org/<username>/-/packages/container/forgejo/versions
# 4. Cleanup: RELEASETEAMUSER=<username> RELEASETEAMTOKEn=<apptoken> container-images-pull-verify-push-test.sh test_teardown
#
test_run() {
boot
test_teardown
test_setup
VERIFY_STRING=something
VERIFY_COMMAND="echo $VERIFY_STRING"
echo "================================ TEST BEGIN"
main
echo "================================ TEST END"
}
: ${CI_REPO_OWNER:=dachary}
: ${PULL_USER:=$CI_REPO_OWNER}
: ${PUSH_USER:=$CI_REPO_OWNER}
: ${CI_COMMIT_TAG:=v17.1.42-2}
. $(dirname $0)/container-images-pull-verify-push.sh

View File

@ -0,0 +1,122 @@
#!/bin/sh
set -ex
: ${DOCKER_HOST:=unix:///var/run/docker.sock}
: ${ARCHS:=amd64 arm64}
: ${PULL_USER:=forgejo-integration}
if test "$CI_REPO" = "forgejo/release" ; then
: ${PUSH_USER:=forgejo}
else
: ${PUSH_USER:=forgejo-experimental}
fi
: ${INTEGRATION_IMAGE:=codeberg.org/$PULL_USER/forgejo}
: ${TAG:=${CI_COMMIT_TAG##v}}
: ${SHORT_TAG=${TAG%.*-*}}
: ${DOMAIN:=codeberg.org}
: ${TOKEN_HEADER:=/tmp/token$$}
trap "rm -f ${TOKEN_HEADER}" EXIT
: ${VERIFY:=true}
VERIFY_COMMAND='gitea --version'
VERIFY_STRING='built with'
publish() {
for suffix in '' '-rootless' ; do
images=""
for arch in $ARCHS ; do
#
# Get the image from the integration user
#
image=$(image_name $PULL_USER $suffix)
docker pull --platform linux/$arch $image
#
# Verify it is usable
#
if $VERIFY ; then
docker run --platform linux/$arch --rm $image $VERIFY_COMMAND | grep "$VERIFY_STRING"
fi
#
# Push the image with a tag reflecting the architecture to the repo owner
#
arch_image=$(arch_image_name $PUSH_USER $arch $suffix)
docker tag $image $arch_image
docker push $arch_image
images="$images $arch_image"
done
#
# Push a manifest with all the architectures to the repo owner
#
manifest=$(image_name $PUSH_USER $suffix)
docker manifest rm $manifest || true
docker manifest create $manifest $images
image_put $PUSH_USER $(image_tag $suffix) $manifest
image_put $PUSH_USER $(short_image_tag $suffix) $manifest
#
# Sanity check to ensure the manifest that are published can actualy
# be used.
#
for arch in $ARCHS ; do
docker pull --platform linux/$arch $(image_name $PUSH_USER $suffix)
docker pull --platform linux/$arch $(short_image_name $PUSH_USER $suffix)
done
done
}
boot() {
if docker version ; then
return
fi
apk --update --no-cache add coredns jq curl
( echo ".:53 {" ; echo " forward . /etc/resolv.conf"; echo "}" ) > /etc/coredns/Corefile
coredns -conf /etc/coredns/Corefile &
/usr/local/bin/dockerd --data-root /var/lib/docker --host=$DOCKER_HOST --dns 172.17.0.3 &
for i in $(seq 60) ; do
docker version && break
sleep 1
done
docker version || exit 1
}
authenticate() {
echo "$RELEASETEAMTOKEN" | docker login --password-stdin --username "$RELEASETEAMUSER" $DOMAIN
curl -u$RELEASETEAMUSER:$RELEASETEAMTOKEN -sS https://$DOMAIN/v2/token | jq --raw-output '"Authorization: token \(.token)"' > $TOKEN_HEADER
}
image_put() {
docker manifest inspect $3 > /tmp/manifest.json
curl -sS -H @$TOKEN_HEADER -X PUT --data-binary @/tmp/manifest.json https://$DOMAIN/v2/$1/forgejo/manifests/$2
}
main() {
boot
authenticate
publish
}
image_name() {
echo $DOMAIN/$1/forgejo:$(image_tag $2)
}
image_tag() {
echo $TAG$1
}
short_image_name() {
echo $DOMAIN/$1/forgejo:$(short_image_tag $2)
}
short_image_tag() {
echo $SHORT_TAG$1
}
arch_image_name() {
echo $DOMAIN/$1/forgejo:$(arch_image_tag $2 $3)
}
arch_image_tag() {
echo $TAG-$1$2
}
${@:-main}

View File

@ -0,0 +1,107 @@
platform: linux/amd64
when:
event: tag
tag: v*
variables:
- &node_image 'node:18'
- &golang_image 'golang:1.20'
- &alpine_image 'alpine:3.17'
- &gpg_sign_image 'plugins/gpgsign:1'
- &xgo_image 'techknowlogick/xgo:go-1.19.x'
- &gpg_sign_image 'plugins/gpgsign:1'
- &goproxy_override ''
- &goproxy_setup |-
if [ -n "$${GOPROXY_OVERRIDE:-}" ]; then
export GOPROXY="$${GOPROXY_OVERRIDE}";
echo "Using goproxy from goproxy_override \"$${GOPROXY}\"";
elif [ -n "$${GOPROXY_DEFAULT:-}" ]; then
export GOPROXY="$${GOPROXY_DEFAULT}";
echo "Using goproxy from goproxy_default (secret) not displaying";
else
export GOPROXY="https://proxy.golang.org,direct";
echo "No goproxy overrides or defaults given, using \"$${GOPROXY}\"";
fi
workspace:
base: /source
path: /
pipeline:
fetch-tags:
image: *golang_image
pull: true
group: deps
commands:
- git config --add safe.directory '*'
- git fetch --tags --force
deps-frontend:
image: *node_image
pull: true
group: deps
commands:
- make deps-frontend
deps-backend:
image: *golang_image
pull: true
group: deps
environment:
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
commands:
- *goproxy_setup
- make deps-backend
static:
image: *xgo_image
pull: true
commands:
- *goproxy_setup
- curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get -qqy install nodejs
- export PATH=$PATH:$GOPATH/bin
- make CI=true LINUX_ARCHS=linux/amd64,linux/arm64,linux/arm-6 release
environment:
TAGS: 'bindata sqlite sqlite_unlock_notify'
DEBIAN_FRONTEND: 'noninteractive'
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
#
# See https://codeberg.org/forgejo/forgejo/issues/230 for a discussion on this
# compilation stage. The goal is just to verify the build does not break, not that
# the binary produced actually works.
#
freebsd:
image: *xgo_image
group: build
commands:
- *goproxy_setup
- export PATH=$PATH:$GOPATH/bin
- make CI=false release-freebsd
environment:
TAGS: 'bindata sqlite sqlite_unlock_notify'
GOPROXY_OVERRIDE: *goproxy_override
secrets:
- goproxy_default
verifyruns:
image: *golang_image
commands:
- ./dist/release/forgejo-*-amd64 --version | grep 'built with'
- apt-get update
- apt-get install -y qemu-user-static
- /usr/bin/qemu-aarch64-static ./dist/release/forgejo-*-arm64 --version | grep 'built with'
- /usr/bin/qemu-arm-static ./dist/release/forgejo-*-arm-6 --version | grep 'built with'
push-integration:
image: *alpine_image
commands:
- PUSH_USER=$CI_REPO_OWNER releases/binaries-pull-push.sh push
secrets:
- releaseteamtoken
- releaseteamuser

View File

@ -0,0 +1,65 @@
platform: linux/amd64
when:
event: tag
tag: v*
variables:
- &golang_image 'golang:1.20'
- &dind_image 'docker:20.10-dind'
- &buildx_image 'woodpeckerci/plugin-docker-buildx:2.0.0'
- &integration_image 'codeberg.org/forgejo-integration/forgejo'
- &dockerfile_root 'Dockerfile'
# for testing purposes
# - &dockerfile_root 'releases/Dockerfile'
- &dockerfile_rootless 'Dockerfile.rootless'
# for testing purposes
# - &dockerfile_rootless 'releases/Dockerfile-rootless'
- &verify 'true'
# for testing purposes
# - &verify 'false'
- &archs 'amd64 arm64'
pipeline:
fetch-tags:
image: *golang_image
pull: true
commands:
- git config --add safe.directory '*'
- git fetch --tags --force
build-root:
image: *buildx_image
group: integration
pull: true
settings:
platforms: linux/amd64,linux/arm64
dockerfile: *dockerfile_root
registry:
from_secret: domain
tag: ${CI_COMMIT_TAG##v}
repo: *integration_image
build_args:
- GOPROXY=https://proxy.golang.org
password:
from_secret: releaseteamtoken
username:
from_secret: releaseteamuser
build-rootless:
image: *buildx_image
group: integration
pull: true
settings:
platforms: linux/amd64,linux/arm64
dockerfile: *dockerfile_rootless
registry:
from_secret: domain
tag: ${CI_COMMIT_TAG##v}-rootless
repo: *integration_image
build_args:
- GOPROXY=https://proxy.golang.org
password:
from_secret: releaseteamtoken
username:
from_secret: releaseteamuser

View File

@ -0,0 +1,34 @@
platform: linux/amd64
when:
event: push
variables:
- &dind_image 'docker:20.10-dind'
- &alpine_image 'alpine:3.17'
pipeline:
container-images-pull-verify-push:
image: *dind_image
group: integration
commands:
# arm64 would require qemu-user-static which is not available on alpline
# the test coverage does not change much and running the tests test locally
# is possible if there is a doubt
- ARCHS=amd64 ./releases/container-images-pull-verify-push-test.sh test_run
- ./releases/container-images-pull-verify-push-test.sh test_teardown
secrets:
- releaseteamuser
- releaseteamtoken
- domain
binaries-pull-push:
image: *alpine_image
group: integration
commands:
- ./releases/binaries-pull-push-test.sh test_run
- ./releases/binaries-pull-push-test.sh test_teardown
secrets:
- releaseteamuser
- releaseteamtoken
- domain

View File

@ -0,0 +1,36 @@
platform: linux/amd64
when:
event: tag
variables:
- &dind_image 'docker:20.10-dind'
- &gpg_sign_image 'plugins/gpgsign:1'
pipeline:
pull:
image: *dind_image
commands:
- ./releases/binaries-pull-push.sh pull
gpg-sign:
image: *gpg_sign_image
pull: true
settings:
detach_sign: true
excludes:
- "dist/release/*.sha256"
files:
- "dist/release/*"
key:
from_secret: releaseteamgpg
push:
image: *dind_image
commands:
- ./releases/binaries-pull-push.sh push
secrets:
- releaseteamtoken
- releaseteamuser
- domain

View File

@ -0,0 +1,27 @@
platform: linux/amd64
when:
event: tag
variables:
- &dind_image 'docker:20.10-dind'
- &integration_image 'codeberg.org/forgejo-integration/forgejo'
- &verify 'true'
# for testing purposes
# - &verify 'false'
- &archs 'amd64 arm64'
pipeline:
publish:
image: *dind_image
environment:
INTEGRATION_IMAGE: *integration_image
VERIFY: *verify
ARCHS: *archs
commands:
- ./releases/container-images-pull-verify-push.sh
secrets:
- releaseteamtoken
- releaseteamuser
- domain

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
actions_service "code.gitea.io/gitea/services/actions"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
@ -55,9 +56,10 @@ func (s *Service) Register(
}
// create new runner
name, _ := util.SplitStringAtByteN(req.Msg.Name, 255)
runner := &actions_model.ActionRunner{
UUID: gouuid.New().String(),
Name: req.Msg.Name,
Name: name,
OwnerID: runnerToken.OwnerID,
RepoID: runnerToken.RepoID,
AgentLabels: req.Msg.AgentLabels,
@ -148,7 +150,7 @@ func (s *Service) UpdateTask(
}
if err := actions_service.CreateCommitStatus(ctx, task.Job); err != nil {
log.Error("Update commit status failed: %v", err)
log.Error("Update commit status for job %v failed: %v", task.Job.ID, err)
// go on
}

View File

@ -4,13 +4,13 @@
package org
import (
"fmt"
"net/http"
"strconv"
"strings"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
@ -84,13 +84,12 @@ func CreateLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 {
form.Color = "#" + form.Color
}
if !issues_model.LabelColorPattern.MatchString(form.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
color, err := label.NormalizeColor(form.Color)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "Color", err)
return
}
form.Color = color
label := &issues_model.Label{
Name: form.Name,
@ -183,7 +182,7 @@ func EditLabel(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption)
label, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) {
ctx.NotFound()
@ -194,30 +193,28 @@ func EditLabel(ctx *context.APIContext) {
}
if form.Name != nil {
label.Name = *form.Name
l.Name = *form.Name
}
if form.Exclusive != nil {
label.Exclusive = *form.Exclusive
l.Exclusive = *form.Exclusive
}
if form.Color != nil {
label.Color = strings.Trim(*form.Color, " ")
if len(label.Color) == 6 {
label.Color = "#" + label.Color
}
if !issues_model.LabelColorPattern.MatchString(label.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
color, err := label.NormalizeColor(*form.Color)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "Color", err)
return
}
l.Color = color
}
if form.Description != nil {
label.Description = *form.Description
l.Description = *form.Description
}
if err := issues_model.UpdateLabel(label); err != nil {
if err := issues_model.UpdateLabel(l); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return
}
ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser()))
ctx.JSON(http.StatusOK, convert.ToLabel(l, nil, ctx.Org.Organization.AsUser()))
}
// DeleteLabel delete a label for an organization

View File

@ -5,13 +5,12 @@
package repo
import (
"fmt"
"net/http"
"strconv"
"strings"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
@ -93,14 +92,14 @@ func GetLabel(ctx *context.APIContext) {
// "$ref": "#/responses/Label"
var (
label *issues_model.Label
err error
l *issues_model.Label
err error
)
strID := ctx.Params(":id")
if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
label, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
l, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
} else {
label, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
l, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
}
if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) {
@ -111,7 +110,7 @@ func GetLabel(ctx *context.APIContext) {
return
}
ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil))
}
// CreateLabel create a label for a repository
@ -145,28 +144,27 @@ func CreateLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 {
form.Color = "#" + form.Color
}
if !issues_model.LabelColorPattern.MatchString(form.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
color, err := label.NormalizeColor(form.Color)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
return
}
form.Color = color
label := &issues_model.Label{
l := &issues_model.Label{
Name: form.Name,
Exclusive: form.Exclusive,
Color: form.Color,
RepoID: ctx.Repo.Repository.ID,
Description: form.Description,
}
if err := issues_model.NewLabel(ctx, label); err != nil {
if err := issues_model.NewLabel(ctx, l); err != nil {
ctx.Error(http.StatusInternalServerError, "NewLabel", err)
return
}
ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil))
ctx.JSON(http.StatusCreated, convert.ToLabel(l, ctx.Repo.Repository, nil))
}
// EditLabel modify a label for a repository
@ -206,7 +204,7 @@ func EditLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption)
label, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) {
ctx.NotFound()
@ -217,30 +215,28 @@ func EditLabel(ctx *context.APIContext) {
}
if form.Name != nil {
label.Name = *form.Name
l.Name = *form.Name
}
if form.Exclusive != nil {
label.Exclusive = *form.Exclusive
l.Exclusive = *form.Exclusive
}
if form.Color != nil {
label.Color = strings.Trim(*form.Color, " ")
if len(label.Color) == 6 {
label.Color = "#" + label.Color
}
if !issues_model.LabelColorPattern.MatchString(label.Color) {
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
color, err := label.NormalizeColor(*form.Color)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
return
}
l.Color = color
}
if form.Description != nil {
label.Description = *form.Description
l.Description = *form.Description
}
if err := issues_model.UpdateLabel(label); err != nil {
if err := issues_model.UpdateLabel(l); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return
}
ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil))
}
// DeleteLabel delete a label for a repository

View File

@ -19,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
@ -248,7 +249,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
} else if db.IsErrNameReserved(err) ||
db.IsErrNamePatternNotAllowed(err) ||
repo_module.IsErrIssueLabelTemplateLoad(err) {
label.IsErrTemplateLoad(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "CreateRepository", err)

View File

@ -16,7 +16,7 @@ import (
"code.gitea.io/gitea/modules/web/routing"
"github.com/chi-middleware/proxy"
"github.com/go-chi/chi/v5/middleware"
chi "github.com/go-chi/chi/v5"
)
// Middlewares returns common middlewares
@ -48,7 +48,8 @@ func Middlewares() []func(http.Handler) http.Handler {
handlers = append(handlers, proxy.ForwardedHeaders(opt))
}
handlers = append(handlers, middleware.StripSlashes)
// Strip slashes.
handlers = append(handlers, stripSlashesMiddleware)
if !setting.Log.DisableRouterLog {
handlers = append(handlers, routing.NewLoggerHandler())
@ -81,3 +82,33 @@ func Middlewares() []func(http.Handler) http.Handler {
})
return handlers
}
func stripSlashesMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
var urlPath string
rctx := chi.RouteContext(req.Context())
if rctx != nil && rctx.RoutePath != "" {
urlPath = rctx.RoutePath
} else if req.URL.RawPath != "" {
urlPath = req.URL.RawPath
} else {
urlPath = req.URL.Path
}
sanitizedPath := &strings.Builder{}
prevWasSlash := false
for _, chr := range strings.TrimRight(urlPath, "/") {
if chr != '/' || !prevWasSlash {
sanitizedPath.WriteRune(chr)
}
prevWasSlash = chr == '/'
}
if rctx == nil {
req.URL.Path = sanitizedPath.String()
} else {
rctx.RoutePath = sanitizedPath.String()
}
next.ServeHTTP(resp, req)
})
}

View File

@ -0,0 +1,70 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStripSlashesMiddleware(t *testing.T) {
type test struct {
name string
expectedPath string
inputPath string
}
tests := []test{
{
name: "path with multiple slashes",
inputPath: "https://github.com///go-gitea//gitea.git",
expectedPath: "/go-gitea/gitea.git",
},
{
name: "path with no slashes",
inputPath: "https://github.com/go-gitea/gitea.git",
expectedPath: "/go-gitea/gitea.git",
},
{
name: "path with slashes in the middle",
inputPath: "https://git.data.coop//halfd/new-website.git",
expectedPath: "/halfd/new-website.git",
},
{
name: "path with slashes in the middle",
inputPath: "https://git.data.coop//halfd/new-website.git",
expectedPath: "/halfd/new-website.git",
},
{
name: "path with slashes in the end",
inputPath: "/user2//repo1/",
expectedPath: "/user2/repo1",
},
{
name: "path with slashes and query params",
inputPath: "/repo//migrate?service_type=3",
expectedPath: "/repo/migrate",
},
{
name: "path with encoded slash",
inputPath: "/user2/%2F%2Frepo1",
expectedPath: "/user2/%2F%2Frepo1",
},
}
for _, tt := range tests {
testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, tt.expectedPath, r.URL.Path)
})
// pass the test middleware to validate the changes
handlerToTest := stripSlashesMiddleware(testMiddleware)
// create a mock request to use
req := httptest.NewRequest("GET", tt.inputPath, nil)
// call the handler using a mock response recorder
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
}
}

View File

@ -150,7 +150,7 @@ func GlobalInitInstalled(ctx context.Context) {
mustInit(system.Init)
mustInit(oauth2.Init)
mustInit(models.Init)
mustInitCtx(ctx, models.Init)
mustInit(repo_service.Init)
// Booting long running goroutines.

View File

@ -59,11 +59,6 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
dbTypeNames := getSupportedDbTypeNames()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if setting.InstallLock {
resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
_ = rnd.HTML(resp, http.StatusOK, string(tplPostInstall), nil)
return
}
locale := middleware.Locale(resp, req)
startTime := time.Now()
ctx := context.Context{
@ -93,6 +88,11 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
// Install render installation page
func Install(ctx *context.Context) {
if setting.InstallLock {
InstallDone(ctx)
return
}
form := forms.InstallForm{}
// Database settings
@ -162,7 +162,7 @@ func Install(ctx *context.Context) {
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
form.NoReplyAddress = setting.Service.NoReplyAddress
form.PasswordAlgorithm = setting.PasswordHashAlgo
form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
middleware.AssignForm(form, ctx.Data)
ctx.HTML(http.StatusOK, tplInstall)
@ -234,6 +234,11 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
// SubmitInstall response for submit install items
func SubmitInstall(ctx *context.Context) {
if setting.InstallLock {
InstallDone(ctx)
return
}
var err error
form := *web.GetForm(ctx).(*forms.InstallForm)
@ -277,7 +282,6 @@ func SubmitInstall(ctx *context.Context) {
setting.Database.Charset = form.Charset
setting.Database.Path = form.DbPath
setting.Database.LogSQL = !setting.IsProd
setting.PasswordHashAlgo = form.PasswordAlgorithm
if !checkDatabase(ctx, &form) {
return
@ -499,6 +503,12 @@ func SubmitInstall(ctx *context.Context) {
}
if len(form.PasswordAlgorithm) > 0 {
var algorithm *hash.PasswordHashAlgorithm
setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm)
if algorithm == nil {
ctx.RenderWithErr(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form)
return
}
cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm)
}
@ -571,18 +581,26 @@ func SubmitInstall(ctx *context.Context) {
}
log.Info("First-time run install finished!")
InstallDone(ctx)
ctx.Flash.Success(ctx.Tr("install.install_success"))
ctx.RespHeader().Add("Refresh", "1; url="+setting.AppURL+"user/login")
ctx.HTML(http.StatusOK, tplPostInstall)
// Now get the http.Server from this request and shut it down
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
srv := ctx.Value(http.ServerContextKey).(*http.Server)
go func() {
// Sleep for a while to make sure the user's browser has loaded the post-install page and its assets (images, css, js)
// What if this duration is not long enough? That's impossible -- if the user can't load the simple page in time, how could they install or use Gitea in the future ....
time.Sleep(3 * time.Second)
// Now get the http.Server from this request and shut it down
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
srv := ctx.Value(http.ServerContextKey).(*http.Server)
if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil {
log.Error("Unable to shutdown the install server! Error: %v", err)
}
// After the HTTP server for "install" shuts down, the `runWeb()` will continue to run the "normal" server
}()
}
// InstallDone shows the "post-install" page, makes it easier to develop the page.
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
func InstallDone(ctx *context.Context) { //nolint
ctx.HTML(http.StatusOK, tplPostInstall)
}

View File

@ -6,6 +6,7 @@ package install
import (
goctx "context"
"fmt"
"html"
"net/http"
"path"
@ -37,7 +38,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
// Why we need this? The first recover will try to render a beautiful
// error page for user, but the process can still panic again, then
// we have to just recover twice and send a simple error page that
// should not panic any more.
// should not panic anymore.
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
@ -107,8 +108,9 @@ func Routes(ctx goctx.Context) *web.Route {
r.Use(installRecovery(ctx))
r.Use(Init(ctx))
r.Get("/", Install)
r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
r.Get("/post-install", InstallDone)
r.Get("/api/healthz", healthcheck.Check)
r.NotFound(web.Wrap(installNotFound))
@ -116,5 +118,10 @@ func Routes(ctx goctx.Context) *web.Route {
}
func installNotFound(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, setting.AppURL, http.StatusFound)
w.Header().Add("Content-Type", "text/html; charset=utf-8")
w.Header().Add("Refresh", fmt.Sprintf("1; url=%s", setting.AppSubURL+"/"))
// do not use 30x status, because the "post-install" page needs to use 404/200 to detect if Gitea has been installed.
// the fetch API could follow 30x requests to the page with 200 status.
w.WriteHeader(http.StatusNotFound)
_, _ = fmt.Fprintf(w, `Not Found. <a href="%s">Go to default page</a>.`, html.EscapeString(setting.AppSubURL+"/"))
}

View File

@ -103,7 +103,7 @@ func Config(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminConfig"] = true
systemSettings, err := system_model.GetAllSettings()
systemSettings, err := system_model.GetAllSettings(ctx)
if err != nil {
ctx.ServerError("system_model.GetAllSettings", err)
return

View File

@ -33,9 +33,10 @@ func Repos(ctx *context.Context) {
ctx.Data["PageIsAdminRepositories"] = true
explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{
Private: true,
PageSize: setting.UI.Admin.RepoPagingNum,
TplName: tplRepos,
Private: true,
PageSize: setting.UI.Admin.RepoPagingNum,
TplName: tplRepos,
OnlyShowRelevant: false,
})
}

View File

@ -23,14 +23,16 @@ const (
// RepoSearchOptions when calling search repositories
type RepoSearchOptions struct {
OwnerID int64
Private bool
Restricted bool
PageSize int
TplName base.TplName
OwnerID int64
Private bool
Restricted bool
PageSize int
OnlyShowRelevant bool
TplName base.TplName
}
// RenderRepoSearch render repositories search page
// This function is also used to render the Admin Repository Management page.
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Sitemap index for sitemap paths
page := int(ctx.ParamsInt64("idx"))
@ -48,11 +50,10 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
}
var (
repos []*repo_model.Repository
count int64
err error
orderBy db.SearchOrderBy
onlyShowRelevant bool
repos []*repo_model.Repository
count int64
err error
orderBy db.SearchOrderBy
)
ctx.Data["SortType"] = ctx.FormString("sort")
@ -84,11 +85,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
orderBy = db.SearchOrderByRecentUpdated
}
onlyShowRelevant = !ctx.FormBool(relevantReposOnlyParam)
keyword := ctx.FormTrim("q")
ctx.Data["OnlyShowRelevant"] = onlyShowRelevant
ctx.Data["OnlyShowRelevant"] = opts.OnlyShowRelevant
topicOnly := ctx.FormBool("topic")
ctx.Data["TopicOnly"] = topicOnly
@ -111,7 +110,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
TopicOnly: topicOnly,
Language: language,
IncludeDescription: setting.UI.SearchRepoDescription,
OnlyShowRelevant: onlyShowRelevant,
OnlyShowRelevant: opts.OnlyShowRelevant,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@ -158,9 +157,10 @@ func Repos(ctx *context.Context) {
}
RenderRepoSearch(ctx, &RepoSearchOptions{
PageSize: setting.UI.ExplorePagingNum,
OwnerID: ownerID,
Private: ctx.Doer != nil,
TplName: tplExploreRepos,
PageSize: setting.UI.ExplorePagingNum,
OwnerID: ownerID,
Private: ctx.Doer != nil,
TplName: tplExploreRepos,
OnlyShowRelevant: !ctx.FormBool(relevantReposOnlyParam),
})
}

View File

@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
@ -103,8 +104,8 @@ func InitializeLabels(ctx *context.Context) {
}
if err := repo_module.InitializeLabels(ctx, ctx.Org.Organization.ID, form.TemplateName, true); err != nil {
if repo_module.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
if label.IsErrTemplateLoad(err) {
originalErr := err.(label.ErrTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
return

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
context_module "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@ -207,15 +208,18 @@ func Rerun(ctx *context_module.Context) {
job.Stopped = 0
if err := db.WithTx(ctx, func(ctx context.Context) error {
if _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped"); err != nil {
return err
}
return actions_service.CreateCommitStatus(ctx, job)
_, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped")
return err
}); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
log.Error("Update commit status for job %v failed: %v", job.ID, err)
// go on
}
ctx.JSON(http.StatusOK, struct{}{})
}
@ -248,9 +252,6 @@ func Cancel(ctx *context_module.Context) {
if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil {
return err
}
if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
return err
}
}
return nil
}); err != nil {
@ -258,6 +259,13 @@ func Cancel(ctx *context_module.Context) {
return
}
for _, job := range jobs {
if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
log.Error("Update commit status for job %v failed: %v", job.ID, err)
// go on
}
}
ctx.JSON(http.StatusOK, struct{}{})
}

View File

@ -228,7 +228,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
}
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusForbidden, "User permission denied")
ctx.PlainText(http.StatusNotFound, "Repository not found")
return
}
}

View File

@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/web"
@ -41,8 +42,8 @@ func InitializeLabels(ctx *context.Context) {
}
if err := repo_module.InitializeLabels(ctx, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil {
if repo_module.IsErrIssueLabelTemplateLoad(err) {
originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
if label.IsErrTemplateLoad(err) {
originalErr := err.(label.ErrTemplateLoad).OriginalError
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
ctx.Redirect(ctx.Repo.RepoLink + "/labels")
return

View File

@ -25,7 +25,7 @@ const (
func NewDiffPatch(ctx *context.Context) {
canCommit := renderCommitRights(ctx)
ctx.Data["TreePath"] = ""
ctx.Data["PageIsPatch"] = true
ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = ""
@ -51,7 +51,7 @@ func NewDiffPatchPost(ctx *context.Context) {
if form.CommitChoice == frmCommitChoiceNewBranch {
branchName = form.NewBranchName
}
ctx.Data["TreePath"] = ""
ctx.Data["PageIsPatch"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["FileContent"] = form.Content
ctx.Data["commit_summary"] = form.CommitSummary
@ -86,13 +86,14 @@ func NewDiffPatchPost(ctx *context.Context) {
message += "\n\n" + form.CommitMessage
}
if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
Message: message,
Content: strings.ReplaceAll(form.Content, "\r", ""),
}); err != nil {
})
if err != nil {
if models.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
branchErr := err.(models.ErrBranchAlreadyExists)
@ -111,6 +112,6 @@ func NewDiffPatchPost(ctx *context.Context) {
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
} else {
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
ctx.Redirect(ctx.Repo.RepoLink + "/commit/" + fileResponse.Commit.SHA)
}
}

View File

@ -186,7 +186,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
return
}
renderReadmeFile(ctx, readmeFile, treeLink)
renderReadmeFile(ctx, readmeFile, fmt.Sprintf("%s/%s", treeLink, readmeFile.name))
}
// localizedExtensions prepends the provided language code with and without a

View File

@ -43,6 +43,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
return fmt.Errorf("find tasks: %w", err)
}
jobs := make([]*actions_model.ActionRunJob, 0, len(tasks))
for _, task := range tasks {
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := actions_model.StopTask(ctx, task.ID, actions_model.StatusFailure); err != nil {
@ -51,7 +52,8 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
if err := task.LoadJob(ctx); err != nil {
return err
}
return CreateCommitStatus(ctx, task.Job)
jobs = append(jobs, task.Job)
return nil
}); err != nil {
log.Warn("Cannot stop task %v: %v", task.ID, err)
// go on
@ -61,6 +63,14 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
remove()
}
}
for _, job := range jobs {
if err := CreateCommitStatus(ctx, job); err != nil {
log.Error("Update commit status for job %v failed: %v", job.ID, err)
// go on
}
}
return nil
}
@ -80,14 +90,16 @@ func CancelAbandonedJobs(ctx context.Context) error {
job.Status = actions_model.StatusCancelled
job.Stopped = now
if err := db.WithTx(ctx, func(ctx context.Context) error {
if _, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped"); err != nil {
return err
}
return CreateCommitStatus(ctx, job)
_, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
return err
}); err != nil {
log.Warn("cancel abandoned job %v: %v", job.ID, err)
// go on
}
if err := CreateCommitStatus(ctx, job); err != nil {
log.Error("Update commit status for job %v failed: %v", job.ID, err)
// go on
}
}
return nil

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