Compare commits

..

9 Commits

Author SHA1 Message Date
wxiaoguang
0bc8bb3cc4
Make issue meta dropdown support Enter, confirm before reloading (#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: Brecht Van Lommel <brecht@blender.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-24 09:26:27 +08:00
sillyguodong
1f09051f2b
Fix SyncOnCommit always return false in API of push_mirrors (#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.
2023-02-23 15:50:33 -06:00
yp05327
ed954b070d
Fix commit name in Apply Patch page (#23086)
Fixes
https://github.com/go-gitea/gitea/issues/22621#issuecomment-1439309200
2023-02-23 15:14:07 -06:00
HesterG
8ed6096158
Add wrapper to author to avoid long name ui problem (#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.
2023-02-23 14:28:18 -06:00
Sven
659cf30b69
Avoid Hugo from adding quote to actions url (#23097) 2023-02-23 12:19:52 -05:00
KN4CK3R
0ae1ed749d
Remove all package data after tests (#22984)
Fixes #21020

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
2023-02-23 22:11:56 +08:00
HesterG
3adfc0f02d
Change style to improve whitespaces trimming inside inline markdown code (#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.
2023-02-23 20:57:03 +08:00
techknowlogick
dd7d6e3ad0
Nest metadata in refactoring docs (#23087)
Whitespace was missing from refactoring docs metadata.

backport label applied so it is included in versioned docs.
2023-02-23 16:25:18 +08:00
Joakim Pettersen
0ce79bb9f6
Improve reverse proxies documentation (#23068)
Add "Traefik with a sub-path" documentation

closes #23047

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2023-02-23 15:34:09 +08:00
30 changed files with 158 additions and 43 deletions

View File

@ -1336,7 +1336,7 @@ PROXY_HOSTS = *.github.com
## Actions (`actions`) ## Actions (`actions`)
- `ENABLED`: **false**: Enable/Disable actions capabilities - `ENABLED`: **false**: Enable/Disable actions capabilities
- `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "https://gitea.com/actions/checkout" for "uses: actions/checkout@v3" - `DEFAULT_ACTIONS_URL`: **https://gitea.com**: Default address to get action plugins, e.g. the default value means downloading from "<https://gitea.com/actions/checkout>" for "uses: actions/checkout@v3"
`DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like `DEFAULT_ACTIONS_URL` indicates where should we find the relative path action plugin. i.e. when use an action in a workflow file like

View File

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

View File

@ -365,3 +365,23 @@ gitea:
``` ```
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik. This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
## Traefik with a sub-path
In case you already have a site, and you want Gitea to share the domain name, you can setup Traefik to serve Gitea under a sub-path by adding the following to your `docker-compose.yaml` (Assuming the provider is docker) :
```yaml
gitea:
image: gitea/gitea
...
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.rule=Host(`example.com`) && PathPrefix(`/gitea`)"
- "traefik.http.services.gitea-websecure.loadbalancer.server.port=3000"
- "traefik.http.middlewares.gitea-stripprefix.stripprefix.prefixes=/gitea"
- "traefik.http.routers.gitea.middlewares=gitea-stripprefix"
```
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.

2
go.mod
View File

@ -117,7 +117,7 @@ require (
mvdan.cc/xurls/v2 v2.4.0 mvdan.cc/xurls/v2 v2.4.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
xorm.io/builder v0.3.12 xorm.io/builder v0.3.12
xorm.io/xorm v1.3.3-0.20221209153726-f1bfc5ce9830 xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e
) )
require ( require (

4
go.sum
View File

@ -2075,5 +2075,5 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM= xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.3-0.20221209153726-f1bfc5ce9830 h1:ohaHCvT7ocSDkTEa2/2z0BXfINYlHm/Z7IzN7MeXQlM= xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e h1:d5PY6mwuQK5/7T6VKfFswaKMzLmGTHkJ/ZS7+cUIAjk=
xorm.io/xorm v1.3.3-0.20221209153726-f1bfc5ce9830/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw=

View File

@ -209,7 +209,7 @@ func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean interface{
return err return err
} }
// DeleteBeans deletes all given beans, beans should contain delete conditions. // DeleteBeans deletes all given beans, beans must contain delete conditions.
func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) { func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) {
e := GetEngine(ctx) e := GetEngine(ctx)
for i := range beans { for i := range beans {
@ -220,6 +220,17 @@ func DeleteBeans(ctx context.Context, beans ...interface{}) (err error) {
return nil return nil
} }
// TruncateBeans deletes all given beans, beans may contain delete conditions.
func TruncateBeans(ctx context.Context, beans ...interface{}) (err error) {
e := GetEngine(ctx)
for i := range beans {
if _, err = e.Truncate(beans[i]); err != nil {
return err
}
}
return nil
}
// CountByBean counts the number of database records according non-empty fields of the bean as conditions. // CountByBean counts the number of database records according non-empty fields of the bean as conditions.
func CountByBean(ctx context.Context, bean interface{}) (int64, error) { func CountByBean(ctx context.Context, bean interface{}) (int64, error) {
return GetEngine(ctx).Count(bean) return GetEngine(ctx).Count(bean)

View File

@ -38,6 +38,7 @@ type Engine interface {
Count(...interface{}) (int64, error) Count(...interface{}) (int64, error)
Decr(column string, arg ...interface{}) *xorm.Session Decr(column string, arg ...interface{}) *xorm.Session
Delete(...interface{}) (int64, error) Delete(...interface{}) (int64, error)
Truncate(...interface{}) (int64, error)
Exec(...interface{}) (sql.Result, error) Exec(...interface{}) (sql.Result, error)
Find(interface{}, ...interface{}) error Find(interface{}, ...interface{}) error
Get(beans ...interface{}) (bool, error) Get(beans ...interface{}) (bool, error)

View File

@ -25,6 +25,8 @@ const (
func NewDiffPatch(ctx *context.Context) { func NewDiffPatch(ctx *context.Context) {
canCommit := renderCommitRights(ctx) canCommit := renderCommitRights(ctx)
ctx.Data["PageIsPatch"] = true
ctx.Data["TreePath"] = "" ctx.Data["TreePath"] = ""
ctx.Data["commit_summary"] = "" ctx.Data["commit_summary"] = ""
@ -51,6 +53,7 @@ func NewDiffPatchPost(ctx *context.Context) {
if form.CommitChoice == frmCommitChoiceNewBranch { if form.CommitChoice == frmCommitChoiceNewBranch {
branchName = form.NewBranchName branchName = form.NewBranchName
} }
ctx.Data["PageIsPatch"] = true
ctx.Data["TreePath"] = "" ctx.Data["TreePath"] = ""
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["FileContent"] = form.Content ctx.Data["FileContent"] = form.Content

View File

@ -24,6 +24,7 @@ func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
LastUpdateUnix: pm.LastUpdateUnix.FormatLong(), LastUpdateUnix: pm.LastUpdateUnix.FormatLong(),
LastError: pm.LastError, LastError: pm.LastError,
Interval: pm.Interval.String(), Interval: pm.Interval.String(),
SyncOnCommit: pm.SyncOnCommit,
}, nil }, nil
} }

View File

@ -9,7 +9,7 @@
{{.locale.Tr "repo.editor.commit_changes"}} {{.locale.Tr "repo.editor.commit_changes"}}
{{- end}}</h3> {{- end}}</h3>
<div class="field"> <div class="field">
<input name="commit_summary" placeholder="{{if .PageIsDelete}}{{.locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{.locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{.locale.Tr "repo.editor.add_tmpl"}}{{else}}{{.locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus> <input name="commit_summary" placeholder="{{if .PageIsDelete}}{{.locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{.locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{.locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{.locale.Tr "repo.editor.patch"}}{{else}}{{.locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
</div> </div>
<div class="field"> <div class="field">
<textarea name="commit_message" placeholder="{{.locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea> <textarea name="commit_message" placeholder="{{.locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea>

View File

@ -7,7 +7,7 @@
<div class="header">{{.ctx.locale.Tr "repo.pick_reaction"}}</div> <div class="header">{{.ctx.locale.Tr "repo.pick_reaction"}}</div>
<div class="divider"></div> <div class="divider"></div>
{{range $value := AllowedReactions}} {{range $value := AllowedReactions}}
<div class="item reaction tooltip" data-content="{{$value}}">{{ReactionToEmoji $value}}</div> <a class="item reaction tooltip" data-content="{{$value}}">{{ReactionToEmoji $value}}</a>
{{end}} {{end}}
</div> </div>
</div> </div>

View File

@ -10,16 +10,16 @@
{{else}} {{else}}
{{$referenceUrl = Printf "%s/files#%s" .ctx.Issue.Link .item.HashTag}} {{$referenceUrl = Printf "%s/files#%s" .ctx.Issue.Link .item.HashTag}}
{{end}} {{end}}
<div class="item context" data-clipboard-text-type="url" data-clipboard-text="{{AppSubUrl}}{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.copy_link"}}</div> <a class="item context" data-clipboard-text-type="url" data-clipboard-text="{{AppSubUrl}}{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.copy_link"}}</a>
<div class="item context quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{.ctx.locale.Tr "repo.issues.context.quote_reply"}}</div> <a class="item context quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{.ctx.locale.Tr "repo.issues.context.quote_reply"}}</a>
{{if not .ctx.UnitIssuesGlobalDisabled}} {{if not .ctx.UnitIssuesGlobalDisabled}}
<div class="item context reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.reference_issue"}}</div> <a class="item context reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.reference_issue"}}</a>
{{end}} {{end}}
{{if or .ctx.Permission.IsAdmin .IsCommentPoster .ctx.HasIssuesOrPullsWritePermission}} {{if or .ctx.Permission.IsAdmin .IsCommentPoster .ctx.HasIssuesOrPullsWritePermission}}
<div class="divider"></div> <div class="divider"></div>
<div class="item context edit-content">{{.ctx.locale.Tr "repo.issues.context.edit"}}</div> <a class="item context edit-content">{{.ctx.locale.Tr "repo.issues.context.edit"}}</a>
{{if .delete}} {{if .delete}}
<div class="item context delete-comment" data-comment-id={{.item.HashTag}} data-url="{{.ctx.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{.ctx.locale.Tr "repo.issues.delete_comment_confirm"}}">{{.ctx.locale.Tr "repo.issues.context.delete"}}</div> <a class="item context delete-comment" data-comment-id={{.item.HashTag}} data-url="{{.ctx.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{.ctx.locale.Tr "repo.issues.delete_comment_confirm"}}">{{.ctx.locale.Tr "repo.issues.context.delete"}}</a>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

View File

@ -121,7 +121,7 @@
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_labels"}}"> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_labels"}}">
</div> </div>
{{end}} {{end}}
<div class="no-select item">{{.locale.Tr "repo.issues.new.clear_labels"}}</div> <a class="no-select item" href="#">{{.locale.Tr "repo.issues.new.clear_labels"}}</a>
{{if or .Labels .OrgLabels}} {{if or .Labels .OrgLabels}}
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}} {{range .Labels}}

View File

@ -8,14 +8,14 @@
{{if .LatestCommitUser}} {{if .LatestCommitUser}}
{{avatar $.Context .LatestCommitUser 24}} {{avatar $.Context .LatestCommitUser 24}}
{{if .LatestCommitUser.FullName}} {{if .LatestCommitUser.FullName}}
<a class="muted" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a> <a class="muted author-wrapper" title="{{.LatestCommitUser.FullName}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
{{else}} {{else}}
<a class="muted" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a> <a class="muted author-wrapper" title="{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}" href="{{.LatestCommitUser.HomeLink}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
{{end}} {{end}}
{{else}} {{else}}
{{if .LatestCommit.Author}} {{if .LatestCommit.Author}}
{{avatarByEmail $.Context .LatestCommit.Author.Email .LatestCommit.Author.Name 24}} {{avatarByEmail $.Context .LatestCommit.Author.Email .LatestCommit.Author.Name 24}}
<strong>{{.LatestCommit.Author.Name}}</strong> <span class="author-wrapper" title="{{.LatestCommit.Author.Name}}"><strong>{{.LatestCommit.Author.Name}}</strong></span>
{{end}} {{end}}
{{end}} {{end}}
<a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified}} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}" href="{{.RepoLink}}/commit/{{PathEscape .LatestCommit.ID.String}}"> <a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified}} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}" href="{{.RepoLink}}/commit/{{PathEscape .LatestCommit.ID.String}}">

View File

@ -25,6 +25,7 @@ import (
func TestPackageComposer(t *testing.T) { func TestPackageComposer(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
vendorName := "gitea" vendorName := "gitea"

View File

@ -205,6 +205,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha
func TestPackageConan(t *testing.T) { func TestPackageConan(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
name := "ConanPackage" name := "ConanPackage"

View File

@ -640,7 +640,7 @@ func TestPackageContainer(t *testing.T) {
checkCatalog := func(owner string) func(t *testing.T) { checkCatalog := func(owner string) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL)) req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL))
addTokenAuthHeader(req, userToken) addTokenAuthHeader(req, userToken)

View File

@ -21,6 +21,7 @@ import (
func TestPackageGeneric(t *testing.T) { func TestPackageGeneric(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "te-st_pac.kage" packageName := "te-st_pac.kage"

View File

@ -26,6 +26,7 @@ import (
func TestPackageHelm(t *testing.T) { func TestPackageHelm(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "test-chart" packageName := "test-chart"

View File

@ -22,6 +22,7 @@ import (
func TestPackageMaven(t *testing.T) { func TestPackageMaven(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
groupID := "com.gitea" groupID := "com.gitea"

View File

@ -24,6 +24,7 @@ import (
func TestPackageNpm(t *testing.T) { func TestPackageNpm(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name))) token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name)))

View File

@ -27,6 +27,7 @@ import (
func TestPackagePub(t *testing.T) { func TestPackagePub(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
token := "Bearer " + getUserToken(t, user.Name) token := "Bearer " + getUserToken(t, user.Name)

View File

@ -25,6 +25,7 @@ import (
func TestPackagePyPI(t *testing.T) { func TestPackagePyPI(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "test-package" packageName := "test-package"

View File

@ -23,6 +23,7 @@ import (
func TestPackageRubyGems(t *testing.T) { func TestPackageRubyGems(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
packageName := "gitea" packageName := "gitea"

View File

@ -19,6 +19,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup" packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -235,16 +236,35 @@ func TestPackageQuota(t *testing.T) {
func TestPackageCleanup(t *testing.T) { func TestPackageCleanup(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
duration, _ := time.ParseDuration("-1h") duration, _ := time.ParseDuration("-1h")
t.Run("Common", func(t *testing.T) { t.Run("Common", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
// Upload and delete a generic package and upload a container blob
data, _ := util.CryptoRandomBytes(5)
url := fmt.Sprintf("/api/packages/%s/generic/cleanup-test/1.1.1/file.bin", user.Name)
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(data))
AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", url)
AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)
data, _ = util.CryptoRandomBytes(5)
url = fmt.Sprintf("/v2/%s/cleanup-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256(data))
req = NewRequestWithBody(t, "POST", url, bytes.NewReader(data))
AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusCreated)
pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, duration) pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, duration)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, pbs) assert.NotEmpty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
assert.NoError(t, err) assert.NoError(t, err)
err = packages_cleanup_service.Cleanup(db.DefaultContext, duration) err = packages_cleanup_service.Cleanup(db.DefaultContext, duration)
@ -254,15 +274,13 @@ func TestPackageCleanup(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, pbs) assert.Empty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
}) })
t.Run("CleanupRules", func(t *testing.T) { t.Run("CleanupRules", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
type version struct { type version struct {
Version string Version string
ShouldExist bool ShouldExist bool

View File

@ -13,6 +13,8 @@ import (
"runtime" "runtime"
"testing" "testing"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -204,6 +206,18 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() {
return err return err
})) }))
// clear all package data
assert.NoError(t, db.TruncateBeans(db.DefaultContext,
&packages_model.Package{},
&packages_model.PackageVersion{},
&packages_model.PackageFile{},
&packages_model.PackageBlob{},
&packages_model.PackageProperty{},
&packages_model.PackageBlobUpload{},
&packages_model.PackageCleanupRule{},
))
assert.NoError(t, storage.Clean(storage.Packages))
return deferFn return deferFn
} }

View File

@ -81,7 +81,8 @@ function attachOneDropdownAria($dropdown) {
$dropdown.on('keydown', (e) => { $dropdown.on('keydown', (e) => {
// here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler // here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler
if (e.key === 'Enter') { if (e.key === 'Enter') {
const $item = $dropdown.dropdown('get item', $dropdown.dropdown('get value')); let $item = $dropdown.dropdown('get item', $dropdown.dropdown('get value'));
if (!$item) $item = $menu.find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
// if the selected item is clickable, then trigger the click event. in the future there could be a special CSS class for it. // if the selected item is clickable, then trigger the click event. in the future there could be a special CSS class for it.
if ($item && $item.is('a')) $item[0].click(); if ($item && $item.is('a')) $item[0].click();
} }

View File

@ -29,6 +29,26 @@ import {hideElem, showElem} from '../utils/dom.js';
const {csrfToken} = window.config; const {csrfToken} = window.config;
// if there are draft comments (more than 20 chars), confirm before reloading, to avoid losing comments
function reloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector('.edit-content-zone:not(.gt-hidden) textarea'),
document.querySelector('.edit_area'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 or 20 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 20) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
export function initRepoCommentForm() { export function initRepoCommentForm() {
const $commentForm = $('.comment.form'); const $commentForm = $('.comment.form');
if ($commentForm.length === 0) { if ($commentForm.length === 0) {
@ -86,12 +106,15 @@ export function initRepoCommentForm() {
let hasUpdateAction = $listMenu.data('action') === 'update'; let hasUpdateAction = $listMenu.data('action') === 'update';
const items = {}; const items = {};
$(`.${selector}`).dropdown('setting', 'onHide', () => { $(`.${selector}`).dropdown({
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var 'action': 'nothing', // do not hide the menu if user presses Enter
if (hasUpdateAction) { fullTextSearch: 'exact',
// TODO: Add batch functionality and make this 1 network request. async onHide() {
(async function() { hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
for (const [elementId, item] of Object.entries(items)) { if (hasUpdateAction) {
// TODO: Add batch functionality and make this 1 network request.
const itemEntries = Object.entries(items);
for (const [elementId, item] of itemEntries) {
await updateIssuesMeta( await updateIssuesMeta(
item['update-url'], item['update-url'],
item.action, item.action,
@ -99,9 +122,11 @@ export function initRepoCommentForm() {
elementId, elementId,
); );
} }
window.location.reload(); if (itemEntries.length) {
})(); reloadConfirmDraftComment();
} }
}
},
}); });
$listMenu.find('.item:not(.no-select)').on('click', function (e) { $listMenu.find('.item:not(.no-select)').on('click', function (e) {
@ -196,7 +221,7 @@ export function initRepoCommentForm() {
'clear', 'clear',
$listMenu.data('issue-id'), $listMenu.data('issue-id'),
'', '',
).then(() => window.location.reload()); ).then(reloadConfirmDraftComment);
} }
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
@ -239,7 +264,7 @@ export function initRepoCommentForm() {
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(() => window.location.reload()); ).then(reloadConfirmDraftComment);
} }
let icon = ''; let icon = '';
@ -272,7 +297,7 @@ export function initRepoCommentForm() {
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(() => window.location.reload()); ).then(reloadConfirmDraftComment);
} }
$list.find('.selected').html(''); $list.find('.selected').html('');

View File

@ -237,6 +237,8 @@
} }
#repo-files-table { #repo-files-table {
table-layout: fixed;
thead { thead {
th { th {
padding-top: 8px; padding-top: 8px;
@ -2885,7 +2887,8 @@ tbody.commit-list {
vertical-align: baseline; vertical-align: baseline;
} }
.message-wrapper { .message-wrapper,
.author-wrapper {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: calc(100% - 50px); max-width: calc(100% - 50px);
@ -2893,6 +2896,10 @@ tbody.commit-list {
vertical-align: middle; vertical-align: middle;
} }
.author-wrapper {
max-width: 180px;
}
// in the commit list, messages can wrap so we can use inline // in the commit list, messages can wrap so we can use inline
.commit-list .message-wrapper { .commit-list .message-wrapper {
display: inline; display: inline;
@ -2912,6 +2919,10 @@ tbody.commit-list {
display: block; display: block;
max-width: calc(100vw - 70px); max-width: calc(100vw - 70px);
} }
.author-wrapper {
max-width: 80px;
}
} }
@media @mediaMd { @media @mediaMd {
@ -2920,7 +2931,7 @@ tbody.commit-list {
} }
th .message-wrapper { th .message-wrapper {
max-width: 280px; max-width: 120px;
} }
} }
@ -2930,7 +2941,7 @@ tbody.commit-list {
} }
th .message-wrapper { th .message-wrapper {
max-width: 490px; max-width: 350px;
} }
} }
@ -2940,7 +2951,7 @@ tbody.commit-list {
} }
th .message-wrapper { th .message-wrapper {
max-width: 680px; max-width: 525px;
} }
} }

View File

@ -415,6 +415,7 @@
padding: .2em .4em; padding: .2em .4em;
margin: 0; margin: 0;
font-size: 85%; font-size: 85%;
white-space: break-spaces;
background-color: var(--color-markup-code-block); background-color: var(--color-markup-code-block);
border-radius: 4px; border-radius: 4px;
} }