forked from mirror/gitea
Compare commits
21 Commits
7f856d5d74
...
83ba882bab
Author | SHA1 | Date | |
---|---|---|---|
Lunny Xiao | 83ba882bab | ||
6543 | 712e19fa6f | ||
wxiaoguang | df60dbfb99 | ||
wxiaoguang | e01b0014de | ||
wxiaoguang | 3e94ac5c7c | ||
KN4CK3R | 85c59d6c21 | ||
Lunny Xiao | 66edc888ee | ||
silverwind | 9b1a8888fa | ||
6543 | 7fd0a5b276 | ||
Lunny Xiao | 67e9f0d498 | ||
6543 | 9a93b1816e | ||
silverwind | 857243bed7 | ||
Denys Konovalov | 225fc40528 | ||
JakobDev | 3e7ae79f99 | ||
6543 | 36de5b299b | ||
Lunny Xiao | e5e2b2fcd7 | ||
Lunny Xiao | 171d3d9a3c | ||
Lunny Xiao | d8bd6f34f0 | ||
Lunny Xiao | aed3b53abd | ||
Yarden Shoham | 75a9f61f89 | ||
Lunny Xiao | e84e5db6de |
|
@ -1,36 +1,84 @@
|
|||
modifies/docs:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
|
||||
modifies/frontend:
|
||||
- "web_src/**/*"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "web_src/**"
|
||||
- "tailwind.config.js"
|
||||
- "webpack.config.js"
|
||||
|
||||
modifies/templates:
|
||||
- all: ["templates/**", "!templates/swagger/v1_json.tmpl"]
|
||||
- changed-files:
|
||||
- all-globs-to-any-file:
|
||||
- "templates/**"
|
||||
- "!templates/swagger/v1_json.tmpl"
|
||||
|
||||
modifies/api:
|
||||
- "routers/api/**"
|
||||
- "templates/swagger/v1_json.tmpl"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "routers/api/**"
|
||||
- "templates/swagger/v1_json.tmpl"
|
||||
|
||||
modifies/cli:
|
||||
- "cmd/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "cmd/**"
|
||||
|
||||
modifies/translation:
|
||||
- "options/locale/*.ini"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "options/locale/*.ini"
|
||||
|
||||
modifies/migrations:
|
||||
- "models/migrations/**/*"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "models/migrations/**"
|
||||
|
||||
modifies/internal:
|
||||
- "Makefile"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
- "docker/**"
|
||||
- "webpack.config.js"
|
||||
- ".eslintrc.yaml"
|
||||
- ".golangci.yml"
|
||||
- ".markdownlint.yaml"
|
||||
- ".spectral.yaml"
|
||||
- ".stylelintrc.yaml"
|
||||
- ".yamllint.yaml"
|
||||
- ".github/**"
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".air.toml"
|
||||
- "Makefile"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile.rootless"
|
||||
- ".dockerignore"
|
||||
- "docker/**"
|
||||
- ".editorconfig"
|
||||
- ".eslintrc.yaml"
|
||||
- ".golangci.yml"
|
||||
- ".gitpod.yml"
|
||||
- ".markdownlint.yaml"
|
||||
- ".spectral.yaml"
|
||||
- ".stylelintrc.yaml"
|
||||
- ".yamllint.yaml"
|
||||
- ".github/**"
|
||||
- ".gitea/"
|
||||
- ".devcontainer/**"
|
||||
- "build.go"
|
||||
- "build/**"
|
||||
- "contrib/**"
|
||||
|
||||
modifies/dependencies:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
- "poetry.toml"
|
||||
- "poetry.lock"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "pyproject.toml"
|
||||
|
||||
modifies/go:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "**/*.go"
|
||||
|
||||
modifies/js:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "**/*.js"
|
||||
|
|
|
@ -9,12 +9,12 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
label:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v4
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
dot: true
|
||||
sync-labels: true
|
||||
|
|
|
@ -53,7 +53,7 @@ HTML 页面由[Go HTML Template](https://pkg.go.dev/html/template)渲染。
|
|||
### 可访问性 / ARIA
|
||||
|
||||
在历史上,Gitea大量使用了可访问性不友好的框架 Fomantic UI。
|
||||
Gitea使用一些补丁使Fomantic UI更具可访问性(参见`aria.js`和`aria.md`),
|
||||
Gitea 使用一些补丁使 Fomantic UI 更具可访问性(参见 `aria.md`),
|
||||
但仍然存在许多问题需要大量的工作和时间来修复。
|
||||
|
||||
### 框架使用
|
||||
|
|
4
go.mod
4
go.mod
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/bufbuild/connect-go v1.10.0
|
||||
|
@ -172,7 +172,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -104,14 +104,14 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06
|
|||
github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I=
|
||||
github.com/RoaringBitmap/roaring v1.7.0 h1:OZF303tJCER1Tj3x+aArx/S5X7hrT186ri6JjrGvG68=
|
||||
github.com/RoaringBitmap/roaring v1.7.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
|
@ -260,8 +260,8 @@ github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmW
|
|||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
|
|
|
@ -148,6 +148,7 @@ type Action struct {
|
|||
Repo *repo_model.Repository `xorm:"-"`
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Comment *issues_model.Comment `xorm:"-"`
|
||||
Issue *issues_model.Issue `xorm:"-"` // get the issue id from content
|
||||
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
|
@ -290,11 +291,6 @@ func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string {
|
|||
return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx))
|
||||
}
|
||||
|
||||
// GetCommentHTMLURL returns link to action comment.
|
||||
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
|
||||
return a.getCommentHTMLURL(ctx)
|
||||
}
|
||||
|
||||
func (a *Action) loadComment(ctx context.Context) (err error) {
|
||||
if a.CommentID == 0 || a.Comment != nil {
|
||||
return nil
|
||||
|
@ -303,7 +299,8 @@ func (a *Action) loadComment(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func (a *Action) getCommentHTMLURL(ctx context.Context) string {
|
||||
// GetCommentHTMLURL returns link to action comment.
|
||||
func (a *Action) GetCommentHTMLURL(ctx context.Context) string {
|
||||
if a == nil {
|
||||
return "#"
|
||||
}
|
||||
|
@ -311,34 +308,19 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string {
|
|||
if a.Comment != nil {
|
||||
return a.Comment.HTMLURL(ctx)
|
||||
}
|
||||
if len(a.GetIssueInfos()) == 0 {
|
||||
|
||||
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
|
||||
return "#"
|
||||
}
|
||||
// Return link to issue
|
||||
issueIDString := a.GetIssueInfos()[0]
|
||||
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
|
||||
if err != nil {
|
||||
if err := a.Issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
return issue.HTMLURL()
|
||||
return a.Issue.HTMLURL()
|
||||
}
|
||||
|
||||
// GetCommentLink returns link to action comment.
|
||||
func (a *Action) GetCommentLink(ctx context.Context) string {
|
||||
return a.getCommentLink(ctx)
|
||||
}
|
||||
|
||||
func (a *Action) getCommentLink(ctx context.Context) string {
|
||||
if a == nil {
|
||||
return "#"
|
||||
}
|
||||
|
@ -346,26 +328,15 @@ func (a *Action) getCommentLink(ctx context.Context) string {
|
|||
if a.Comment != nil {
|
||||
return a.Comment.Link(ctx)
|
||||
}
|
||||
if len(a.GetIssueInfos()) == 0 {
|
||||
|
||||
if err := a.LoadIssue(ctx); err != nil || a.Issue == nil {
|
||||
return "#"
|
||||
}
|
||||
// Return link to issue
|
||||
issueIDString := a.GetIssueInfos()[0]
|
||||
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
|
||||
if err != nil {
|
||||
if err := a.Issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
issue, err := issues_model.GetIssueByID(ctx, issueID)
|
||||
if err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
if err = issue.LoadRepo(ctx); err != nil {
|
||||
return "#"
|
||||
}
|
||||
|
||||
return issue.Link()
|
||||
return a.Issue.Link()
|
||||
}
|
||||
|
||||
// GetBranch returns the action's repository branch.
|
||||
|
@ -393,6 +364,10 @@ func (a *Action) GetCreate() time.Time {
|
|||
return a.CreatedUnix.AsTime()
|
||||
}
|
||||
|
||||
func (a *Action) IsIssueEvent() bool {
|
||||
return a.OpType.InActions("comment_issue", "approve_pull_request", "reject_pull_request", "comment_pull", "merge_pull_request")
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
|
@ -403,27 +378,52 @@ func (a *Action) GetIssueInfos() []string {
|
|||
return ret
|
||||
}
|
||||
|
||||
// GetIssueTitle returns the title of first issue associated with the action.
|
||||
func (a *Action) GetIssueTitle(ctx context.Context) string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
func (a *Action) getIssueIndex() int64 {
|
||||
infos := a.GetIssueInfos()
|
||||
if len(infos) == 0 {
|
||||
return 0
|
||||
}
|
||||
return issue.Title
|
||||
index, _ := strconv.ParseInt(infos[0], 10, 64)
|
||||
return index
|
||||
}
|
||||
|
||||
// GetIssueContent returns the content of first issue associated with
|
||||
// this action.
|
||||
func (a *Action) GetIssueContent(ctx context.Context) string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
func (a *Action) LoadIssue(ctx context.Context) error {
|
||||
if a.Issue != nil {
|
||||
return nil
|
||||
}
|
||||
return issue.Content
|
||||
if index := a.getIssueIndex(); index > 0 {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Issue = issue
|
||||
a.Issue.Repo = a.Repo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIssueTitle returns the title of first issue associated with the action.
|
||||
func (a *Action) GetIssueTitle(ctx context.Context) string {
|
||||
if err := a.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return "<500 when get issue>"
|
||||
}
|
||||
if a.Issue == nil {
|
||||
return "<Issue not found>"
|
||||
}
|
||||
return a.Issue.Title
|
||||
}
|
||||
|
||||
// GetIssueContent returns the content of first issue associated with this action.
|
||||
func (a *Action) GetIssueContent(ctx context.Context) string {
|
||||
if err := a.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return "<500 when get issue>"
|
||||
}
|
||||
if a.Issue == nil {
|
||||
return "<Content not found>"
|
||||
}
|
||||
return a.Issue.Content
|
||||
}
|
||||
|
||||
// GetFeedsOptions options for retrieving feeds
|
||||
|
@ -463,7 +463,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
|||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||
}
|
||||
|
||||
if err := ActionList(actions).loadAttributes(ctx); err != nil {
|
||||
if err := ActionList(actions).LoadAttributes(ctx); err != nil {
|
||||
return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,16 @@ package activities
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ActionList defines a list of actions
|
||||
|
@ -24,7 +29,7 @@ func (actions ActionList) getUserIDs() []int64 {
|
|||
return userIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||
func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) {
|
||||
if len(actions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -52,7 +57,7 @@ func (actions ActionList) getRepoIDs() []int64 {
|
|||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (actions ActionList) loadRepositories(ctx context.Context) error {
|
||||
func (actions ActionList) LoadRepositories(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -63,11 +68,11 @@ func (actions ActionList) loadRepositories(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("find repository: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
action.Repo = repoMaps[action.RepoID]
|
||||
}
|
||||
return nil
|
||||
repos := repo_model.RepositoryList(util.ValuesOfMap(repoMaps))
|
||||
return repos.LoadUnits(ctx)
|
||||
}
|
||||
|
||||
func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*user_model.User) (err error) {
|
||||
|
@ -75,37 +80,124 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]*
|
|||
userMap = make(map[int64]*user_model.User)
|
||||
}
|
||||
|
||||
userSet := make(container.Set[int64], len(actions))
|
||||
for _, action := range actions {
|
||||
if action.Repo == nil {
|
||||
continue
|
||||
}
|
||||
repoOwner, ok := userMap[action.Repo.OwnerID]
|
||||
if !ok {
|
||||
repoOwner, err = user_model.GetUserByID(ctx, action.Repo.OwnerID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
userMap[repoOwner.ID] = repoOwner
|
||||
if _, ok := userMap[action.Repo.OwnerID]; !ok {
|
||||
userSet.Add(action.Repo.OwnerID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.GetEngine(ctx).
|
||||
In("id", userSet.Values()).
|
||||
Find(&userMap); err != nil {
|
||||
return fmt.Errorf("find user: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if action.Repo != nil {
|
||||
action.Repo.Owner = userMap[action.Repo.OwnerID]
|
||||
}
|
||||
action.Repo.Owner = repoOwner
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadAttributes loads all attributes
|
||||
func (actions ActionList) loadAttributes(ctx context.Context) error {
|
||||
userMap, err := actions.loadUsers(ctx)
|
||||
// LoadAttributes loads all attributes
|
||||
func (actions ActionList) LoadAttributes(ctx context.Context) error {
|
||||
// the load sequence cannot be changed because of the dependencies
|
||||
userMap, err := actions.LoadActUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := actions.loadRepositories(ctx); err != nil {
|
||||
if err := actions.LoadRepositories(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return actions.loadRepoOwner(ctx, userMap)
|
||||
if err := actions.loadRepoOwner(ctx, userMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := actions.LoadIssues(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return actions.LoadComments(ctx)
|
||||
}
|
||||
|
||||
func (actions ActionList) LoadComments(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
commentIDs := make([]int64, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
if action.CommentID > 0 {
|
||||
commentIDs = append(commentIDs, action.CommentID)
|
||||
}
|
||||
}
|
||||
|
||||
commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs))
|
||||
if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil {
|
||||
return fmt.Errorf("find comment: %w", err)
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if action.CommentID > 0 {
|
||||
action.Comment = commentsMap[action.CommentID]
|
||||
if action.Comment != nil {
|
||||
action.Comment.Issue = action.Issue
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (actions ActionList) LoadIssues(ctx context.Context) error {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
conditions := builder.NewCond()
|
||||
issueNum := 0
|
||||
for _, action := range actions {
|
||||
if action.IsIssueEvent() {
|
||||
infos := action.GetIssueInfos()
|
||||
if len(infos) == 0 {
|
||||
continue
|
||||
}
|
||||
index, _ := strconv.ParseInt(infos[0], 10, 64)
|
||||
if index > 0 {
|
||||
conditions = conditions.Or(builder.Eq{
|
||||
"repo_id": action.RepoID,
|
||||
"`index`": index,
|
||||
})
|
||||
issueNum++
|
||||
}
|
||||
}
|
||||
}
|
||||
if !conditions.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
issuesMap := make(map[string]*issues_model.Issue, issueNum)
|
||||
issues := make([]*issues_model.Issue, 0, issueNum)
|
||||
if err := db.GetEngine(ctx).Where(conditions).Find(&issues); err != nil {
|
||||
return fmt.Errorf("find issue: %w", err)
|
||||
}
|
||||
for _, issue := range issues {
|
||||
issuesMap[fmt.Sprintf("%d-%d", issue.RepoID, issue.Index)] = issue
|
||||
}
|
||||
|
||||
for _, action := range actions {
|
||||
if !action.IsIssueEvent() {
|
||||
continue
|
||||
}
|
||||
if index := action.getIssueIndex(); index > 0 {
|
||||
if issue, ok := issuesMap[fmt.Sprintf("%d-%d", action.RepoID, index)]; ok {
|
||||
action.Issue = issue
|
||||
action.Issue.Repo = action.Repo
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
|
@ -29,7 +30,8 @@ type Statistic struct {
|
|||
Mirror, Release, AuthSource, Webhook,
|
||||
Milestone, Label, HookTask,
|
||||
Team, UpdateTask, Project,
|
||||
ProjectBoard, Attachment int64
|
||||
ProjectBoard, Attachment,
|
||||
Branches, Tags, CommitStatus int64
|
||||
IssueByLabel []IssueByLabelCount
|
||||
IssueByRepository []IssueByRepositoryCount
|
||||
}
|
||||
|
@ -58,6 +60,9 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
|
|||
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
|
||||
stats.Counter.Star, _ = e.Count(new(repo_model.Star))
|
||||
stats.Counter.Access, _ = e.Count(new(access_model.Access))
|
||||
stats.Counter.Branches, _ = e.Count(new(git_model.Branch))
|
||||
stats.Counter.Tags, _ = e.Where("is_draft=?", false).Count(new(repo_model.Release))
|
||||
stats.Counter.CommitStatus, _ = e.Count(new(git_model.CommitStatus))
|
||||
|
||||
type IssueCount struct {
|
||||
Count int64
|
||||
|
|
|
@ -673,7 +673,8 @@ func (c *Comment) LoadTime(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository) (err error) {
|
||||
// LoadReactions loads comment reactions
|
||||
func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) (err error) {
|
||||
if c.Reactions != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -691,11 +692,6 @@ func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository
|
|||
return nil
|
||||
}
|
||||
|
||||
// LoadReactions loads comment reactions
|
||||
func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) error {
|
||||
return c.loadReactions(ctx, repo)
|
||||
}
|
||||
|
||||
func (c *Comment) loadReview(ctx context.Context) (err error) {
|
||||
if c.ReviewID == 0 {
|
||||
return nil
|
||||
|
|
|
@ -122,7 +122,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||
}
|
||||
|
||||
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
|
||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) ([]*Comment, error) {
|
||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) (CommentList, error) {
|
||||
opts := FindCommentsOptions{
|
||||
Type: CommentTypeCode,
|
||||
IssueID: issue.ID,
|
||||
|
|
|
@ -19,7 +19,9 @@ type CommentList []*Comment
|
|||
func (comments CommentList) getPosterIDs() []int64 {
|
||||
posterIDs := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
posterIDs.Add(comment.PosterID)
|
||||
if comment.PosterID > 0 {
|
||||
posterIDs.Add(comment.PosterID)
|
||||
}
|
||||
}
|
||||
return posterIDs.Values()
|
||||
}
|
||||
|
@ -41,18 +43,12 @@ func (comments CommentList) LoadPosters(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (comments CommentList) getCommentIDs() []int64 {
|
||||
ids := make([]int64, 0, len(comments))
|
||||
for _, comment := range comments {
|
||||
ids = append(ids, comment.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (comments CommentList) getLabelIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
ids.Add(comment.LabelID)
|
||||
if comment.LabelID > 0 {
|
||||
ids.Add(comment.LabelID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
@ -100,7 +96,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
|
|||
func (comments CommentList) getMilestoneIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
ids.Add(comment.MilestoneID)
|
||||
if comment.MilestoneID > 0 {
|
||||
ids.Add(comment.MilestoneID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
@ -141,7 +139,9 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
|
|||
func (comments CommentList) getOldMilestoneIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
ids.Add(comment.OldMilestoneID)
|
||||
if comment.OldMilestoneID > 0 {
|
||||
ids.Add(comment.OldMilestoneID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
@ -182,7 +182,9 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
|
|||
func (comments CommentList) getAssigneeIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
ids.Add(comment.AssigneeID)
|
||||
if comment.AssigneeID > 0 {
|
||||
ids.Add(comment.AssigneeID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
@ -314,7 +316,9 @@ func (comments CommentList) getDependentIssueIDs() []int64 {
|
|||
if comment.DependentIssue != nil {
|
||||
continue
|
||||
}
|
||||
ids.Add(comment.DependentIssueID)
|
||||
if comment.DependentIssueID > 0 {
|
||||
ids.Add(comment.DependentIssueID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
@ -369,6 +373,41 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getAttachmentCommentIDs only return the comment ids which possibly has attachments
|
||||
func (comments CommentList) getAttachmentCommentIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
if comment.Type == CommentTypeComment ||
|
||||
comment.Type == CommentTypeReview ||
|
||||
comment.Type == CommentTypeCode {
|
||||
ids.Add(comment.ID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
||||
// LoadAttachmentsByIssue loads attachments by issue id
|
||||
func (comments CommentList) LoadAttachmentsByIssue(ctx context.Context) error {
|
||||
if len(comments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
attachments := make([]*repo_model.Attachment, 0, len(comments)/2)
|
||||
if err := db.GetEngine(ctx).Where("issue_id=? AND comment_id>0", comments[0].IssueID).Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commentAttachmentsMap := make(map[int64][]*repo_model.Attachment, len(comments))
|
||||
for _, attach := range attachments {
|
||||
commentAttachmentsMap[attach.CommentID] = append(commentAttachmentsMap[attach.CommentID], attach)
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
comment.Attachments = commentAttachmentsMap[comment.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAttachments loads attachments
|
||||
func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
|
||||
if len(comments) == 0 {
|
||||
|
@ -376,16 +415,15 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
|
|||
}
|
||||
|
||||
attachments := make(map[int64][]*repo_model.Attachment, len(comments))
|
||||
commentsIDs := comments.getCommentIDs()
|
||||
commentsIDs := comments.getAttachmentCommentIDs()
|
||||
left := len(commentsIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).Table("attachment").
|
||||
Join("INNER", "comment", "comment.id = attachment.comment_id").
|
||||
In("comment.id", commentsIDs[:limit]).
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("comment_id", commentsIDs[:limit]).
|
||||
Rows(new(repo_model.Attachment))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -415,7 +453,9 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
|
|||
func (comments CommentList) getReviewIDs() []int64 {
|
||||
ids := make(container.Set[int64], len(comments))
|
||||
for _, comment := range comments {
|
||||
ids.Add(comment.ReviewID)
|
||||
if comment.ReviewID > 0 {
|
||||
ids.Add(comment.ReviewID)
|
||||
}
|
||||
}
|
||||
return ids.Values()
|
||||
}
|
||||
|
|
|
@ -388,9 +388,8 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
|
|||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
rows, err := db.GetEngine(ctx).Table("attachment").
|
||||
Join("INNER", "issue", "issue.id = attachment.issue_id").
|
||||
In("issue.id", issuesIDs[:limit]).
|
||||
rows, err := db.GetEngine(ctx).
|
||||
In("issue_id", issuesIDs[:limit]).
|
||||
Rows(new(repo_model.Attachment))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -476,6 +475,16 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
|
|||
}
|
||||
trackedTimes := make(map[int64]int64, len(issues))
|
||||
|
||||
reposMap := make(map[int64]*repo_model.Repository, len(issues))
|
||||
for _, issue := range issues {
|
||||
reposMap[issue.RepoID] = issue.Repo
|
||||
}
|
||||
repos := repo_model.RepositoryListOfMap(reposMap)
|
||||
|
||||
if err := repos.LoadUnits(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids := make([]int64, 0, len(issues))
|
||||
for _, issue := range issues {
|
||||
if issue.Repo.IsTimetrackerEnabled(ctx) {
|
||||
|
@ -599,3 +608,23 @@ func (issues IssueList) GetApprovalCounts(ctx context.Context) (map[int64][]*Rev
|
|||
|
||||
return approvalCountMap, nil
|
||||
}
|
||||
|
||||
func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error {
|
||||
issueIDs := issues.getIssueIDs()
|
||||
issueUsers := make([]*IssueUser, 0, len(issueIDs))
|
||||
if err := db.GetEngine(ctx).Where("uid =?", userID).
|
||||
In("issue_id").
|
||||
Find(&issueUsers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issueUser := range issueUsers {
|
||||
for _, issue := range issues {
|
||||
if issue.ID == issueUser.IssueID {
|
||||
issue.IsRead = issueUser.IsRead
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -116,12 +116,17 @@ func (l *Label) CalOpenIssues() {
|
|||
func (l *Label) SetArchived(isArchived bool) {
|
||||
if !isArchived {
|
||||
l.ArchivedUnix = timeutil.TimeStamp(0)
|
||||
} else if isArchived && l.ArchivedUnix.IsZero() {
|
||||
} else if isArchived && !l.IsArchived() {
|
||||
// Only change the date when it is newly archived.
|
||||
l.ArchivedUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
}
|
||||
|
||||
// IsArchived returns true if label is an archived
|
||||
func (l *Label) IsArchived() bool {
|
||||
return !l.ArchivedUnix.IsZero()
|
||||
}
|
||||
|
||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
|
||||
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
|
||||
counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
|
||||
|
@ -166,11 +171,6 @@ func (l *Label) BelongsToOrg() bool {
|
|||
return l.OrgID > 0
|
||||
}
|
||||
|
||||
// IsArchived returns true if label is an archived
|
||||
func (l *Label) IsArchived() bool {
|
||||
return l.ArchivedUnix > 0
|
||||
}
|
||||
|
||||
// BelongsToRepo returns true if label is a repository label
|
||||
func (l *Label) BelongsToRepo() bool {
|
||||
return l.RepoID > 0
|
||||
|
|
|
@ -566,6 +566,8 @@ var migrations = []Migration{
|
|||
NewMigration("Add default_wiki_branch to repository table", v1_22.AddDefaultWikiBranch),
|
||||
// v290 -> v291
|
||||
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
|
||||
// v291 -> v292
|
||||
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_22 //nolint
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
func AddCommentIDIndexofAttachment(x *xorm.Engine) error {
|
||||
type Attachment struct {
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
return x.Sync(&Attachment{})
|
||||
}
|
|
@ -24,7 +24,7 @@ type Attachment struct {
|
|||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
|
||||
CommentID int64
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Size int64 `xorm:"DEFAULT 0"`
|
||||
|
|
|
@ -531,6 +531,9 @@ func (repo *Repository) GetBaseRepo(ctx context.Context) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
if repo.BaseRepo != nil {
|
||||
return nil
|
||||
}
|
||||
repo.BaseRepo, err = GetRepositoryByID(ctx, repo.ForkID)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -63,6 +63,41 @@ func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList {
|
|||
return RepositoryList(ValuesRepository(repoMap))
|
||||
}
|
||||
|
||||
func (repos RepositoryList) LoadUnits(ctx context.Context) error {
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load units.
|
||||
units := make([]*RepoUnit, 0, len(repos)*6)
|
||||
if err := db.GetEngine(ctx).
|
||||
In("repo_id", repos.IDs()).
|
||||
Find(&units); err != nil {
|
||||
return fmt.Errorf("find units: %w", err)
|
||||
}
|
||||
|
||||
unitsMap := make(map[int64][]*RepoUnit, len(repos))
|
||||
for _, unit := range units {
|
||||
if !unit.Type.UnitGlobalDisabled() {
|
||||
unitsMap[unit.RepoID] = append(unitsMap[unit.RepoID], unit)
|
||||
}
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
repo.Units = unitsMap[repo.ID]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repos RepositoryList) IDs() []int64 {
|
||||
repoIDs := make([]int64, len(repos))
|
||||
for i := range repos {
|
||||
repoIDs[i] = repos[i].ID
|
||||
}
|
||||
return repoIDs
|
||||
}
|
||||
|
||||
// LoadAttributes loads the attributes for the given RepositoryList
|
||||
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
|
||||
if len(repos) == 0 {
|
||||
|
|
|
@ -118,11 +118,12 @@ func TestReadingBlameOutputSha256(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
assert.NoError(t, err)
|
||||
for _, c := range cases {
|
||||
commit, err := repo.GetCommit(c.CommitID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blameReader, err := CreateBlameReader(ctx, repo.objectFormat, "./tests/repos/repo6_blame_sha256", commit, "blame.txt", c.Bypass)
|
||||
blameReader, err := CreateBlameReader(ctx, objectFormat, "./tests/repos/repo6_blame_sha256", commit, "blame.txt", c.Bypass)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, blameReader)
|
||||
defer blameReader.Close()
|
||||
|
|
|
@ -118,11 +118,13 @@ func TestReadingBlameOutput(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
assert.NoError(t, err)
|
||||
for _, c := range cases {
|
||||
commit, err := repo.GetCommit(c.CommitID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
blameReader, err := CreateBlameReader(ctx, repo.objectFormat, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass)
|
||||
blameReader, err := CreateBlameReader(ctx, objectFormat, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, blameReader)
|
||||
defer blameReader.Close()
|
||||
|
|
|
@ -140,10 +140,13 @@ func TestHasPreviousCommitSha256(t *testing.T) {
|
|||
commit, err := repo.GetCommit("f004f41359117d319dedd0eaab8c5259ee2263da839dcba33637997458627fdc")
|
||||
assert.NoError(t, err)
|
||||
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
assert.NoError(t, err)
|
||||
|
||||
parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c")
|
||||
notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236")
|
||||
assert.Equal(t, repo.objectFormat, parentSHA.Type())
|
||||
assert.Equal(t, repo.objectFormat.Name(), "sha256")
|
||||
assert.Equal(t, objectFormat, parentSHA.Type())
|
||||
assert.Equal(t, objectFormat.Name(), "sha256")
|
||||
|
||||
haz, err := commit.HasPreviousCommit(parentSHA)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -71,11 +71,6 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
|||
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
|
||||
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
|
||||
|
||||
repo.objectFormat, err = repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -246,7 +246,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
|||
}
|
||||
}()
|
||||
|
||||
len := repo.objectFormat.FullLength()
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
len := objectFormat.FullLength()
|
||||
commits := []*Commit{}
|
||||
shaline := make([]byte, len+1)
|
||||
for {
|
||||
|
|
|
@ -41,7 +41,10 @@ func (repo *Repository) RemoveReference(name string) error {
|
|||
|
||||
// ConvertToHash returns a Hash object from a potential ID string
|
||||
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
||||
objectFormat := repo.objectFormat
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(commitID) == hash.HexSize && objectFormat.IsValid(commitID) {
|
||||
ID, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
|
|
|
@ -132,8 +132,11 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID)
|
|||
|
||||
// ConvertToGitID returns a GitHash object from a potential ID string
|
||||
func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
||||
IDType := repo.objectFormat
|
||||
if len(commitID) == IDType.FullLength() && IDType.IsValid(commitID) {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
|
||||
ID, err := NewIDFromString(commitID)
|
||||
if err == nil {
|
||||
return ID, nil
|
||||
|
@ -142,7 +145,7 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
|
|||
|
||||
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx)
|
||||
defer cancel()
|
||||
_, err := wr.Write([]byte(commitID + "\n"))
|
||||
_, err = wr.Write([]byte(commitID + "\n"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -283,8 +283,12 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
|
|||
// If base is undefined empty SHA (zeros), it only returns the files changed in the head commit
|
||||
// If base is the SHA of an empty tree (EmptyTreeSHA), it returns the files changes from the initial commit to the head commit
|
||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
|
||||
if base == repo.objectFormat.EmptyObjectID().String() {
|
||||
if base == objectFormat.EmptyObjectID().String() {
|
||||
cmd.AddDynamicArguments(head)
|
||||
} else {
|
||||
cmd.AddDynamicArguments(base, head)
|
||||
|
|
|
@ -126,17 +126,20 @@ func TestGetCommitFilesChanged(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
assert.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
base, head string
|
||||
files []string
|
||||
}{
|
||||
{
|
||||
repo.objectFormat.EmptyObjectID().String(),
|
||||
objectFormat.EmptyObjectID().String(),
|
||||
"95bb4d39648ee7e325106df01a621c530863a653",
|
||||
[]string{"file1.txt"},
|
||||
},
|
||||
{
|
||||
repo.objectFormat.EmptyObjectID().String(),
|
||||
objectFormat.EmptyObjectID().String(),
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file2.txt"},
|
||||
},
|
||||
|
@ -146,7 +149,7 @@ func TestGetCommitFilesChanged(t *testing.T) {
|
|||
[]string{"file2.txt"},
|
||||
},
|
||||
{
|
||||
repo.objectFormat.EmptyTree().String(),
|
||||
objectFormat.EmptyTree().String(),
|
||||
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||
[]string{"file1.txt", "file2.txt"},
|
||||
},
|
||||
|
|
|
@ -94,6 +94,10 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
|
|||
|
||||
// RemoveFilesFromIndex removes given filenames from the index - it does not check whether they are present.
|
||||
func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := NewCommand(repo.Ctx, "update-index", "--remove", "-z", "--index-info")
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
|
@ -101,7 +105,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
|
|||
for _, file := range filenames {
|
||||
if file != "" {
|
||||
buffer.WriteString("0 ")
|
||||
buffer.WriteString(repo.objectFormat.EmptyObjectID().String())
|
||||
buffer.WriteString(objectFormat.EmptyObjectID().String())
|
||||
buffer.WriteByte('\t')
|
||||
buffer.WriteString(file)
|
||||
buffer.WriteByte('\000')
|
||||
|
|
|
@ -141,7 +141,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
|||
break
|
||||
}
|
||||
|
||||
tag, err := parseTagRef(repo.objectFormat, ref)
|
||||
tag, err := parseTagRef(ref)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err)
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
|
|||
}
|
||||
|
||||
// parseTagRef parses a tag from a 'git for-each-ref'-produced reference.
|
||||
func parseTagRef(objectFormat ObjectFormat, ref map[string]string) (tag *Tag, err error) {
|
||||
func parseTagRef(ref map[string]string) (tag *Tag, err error) {
|
||||
tag = &Tag{
|
||||
Type: ref["objecttype"],
|
||||
Name: ref["refname:lstrip=2"],
|
||||
|
|
|
@ -194,7 +194,6 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRepository_parseTagRef(t *testing.T) {
|
||||
sha1 := Sha1ObjectFormat
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
|
@ -351,7 +350,7 @@ Add changelog of v1.9.1 (#7859)
|
|||
for _, test := range tests {
|
||||
tc := test // don't close over loop variable
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := parseTagRef(sha1, tc.givenRef)
|
||||
got, err := parseTagRef(tc.givenRef)
|
||||
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
|
|
|
@ -21,7 +21,12 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
|||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != repo.objectFormat.FullLength() {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(idStr) != objectFormat.FullLength() {
|
||||
res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -51,7 +51,11 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
|||
case "tree":
|
||||
tree := NewTree(repo, id)
|
||||
tree.ResolvedID = id
|
||||
tree.entries, err = catBatchParseTreeEntries(repo.objectFormat, tree, rd, size)
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tree.entries, err = catBatchParseTreeEntries(objectFormat, tree, rd, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -69,7 +73,11 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
|
|||
|
||||
// GetTree find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
if len(idStr) != repo.objectFormat.FullLength() {
|
||||
objectFormat, err := repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(idStr) != objectFormat.FullLength() {
|
||||
res, err := repo.GetRefCommitID(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -77,8 +77,11 @@ func (t *Tree) ListEntries() (Entries, error) {
|
|||
return nil, runErr
|
||||
}
|
||||
|
||||
var err error
|
||||
t.entries, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
|
||||
objectFormat, err := t.repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.entries, err = parseTreeEntries(objectFormat, stdout, t)
|
||||
if err == nil {
|
||||
t.entriesParsed = true
|
||||
}
|
||||
|
@ -101,8 +104,11 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
|
|||
return nil, runErr
|
||||
}
|
||||
|
||||
var err error
|
||||
t.entriesRecursive, err = parseTreeEntries(t.repo.objectFormat, stdout, t)
|
||||
objectFormat, err := t.repo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.entriesRecursive, err = parseTreeEntries(objectFormat, stdout, t)
|
||||
if err == nil {
|
||||
t.entriesRecursiveParsed = true
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package bleve
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
)
|
||||
|
@ -39,18 +41,18 @@ func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery {
|
|||
return q
|
||||
}
|
||||
|
||||
func NumericRangeInclusiveQuery(min, max *int64, field string) *query.NumericRangeQuery {
|
||||
func NumericRangeInclusiveQuery(min, max optional.Option[int64], field string) *query.NumericRangeQuery {
|
||||
var minF, maxF *float64
|
||||
var minI, maxI *bool
|
||||
if min != nil {
|
||||
if min.Has() {
|
||||
minF = new(float64)
|
||||
*minF = float64(*min)
|
||||
*minF = float64(min.Value())
|
||||
minI = new(bool)
|
||||
*minI = true
|
||||
}
|
||||
if max != nil {
|
||||
if max.Has() {
|
||||
maxF = new(float64)
|
||||
*maxF = float64(*max)
|
||||
*maxF = float64(max.Value())
|
||||
maxI = new(bool)
|
||||
*maxI = true
|
||||
}
|
||||
|
|
|
@ -224,38 +224,41 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
queries = append(queries, bleve.NewDisjunctionQuery(milestoneQueries...))
|
||||
}
|
||||
|
||||
if options.ProjectID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.ProjectID, "project_id"))
|
||||
if options.ProjectID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectID.Value(), "project_id"))
|
||||
}
|
||||
if options.ProjectBoardID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.ProjectBoardID, "project_board_id"))
|
||||
if options.ProjectBoardID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ProjectBoardID.Value(), "project_board_id"))
|
||||
}
|
||||
|
||||
if options.PosterID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.PosterID, "poster_id"))
|
||||
if options.PosterID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.PosterID.Value(), "poster_id"))
|
||||
}
|
||||
|
||||
if options.AssigneeID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.AssigneeID, "assignee_id"))
|
||||
if options.AssigneeID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.AssigneeID.Value(), "assignee_id"))
|
||||
}
|
||||
|
||||
if options.MentionID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.MentionID, "mention_ids"))
|
||||
if options.MentionID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.MentionID.Value(), "mention_ids"))
|
||||
}
|
||||
|
||||
if options.ReviewedID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.ReviewedID, "reviewed_ids"))
|
||||
if options.ReviewedID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ReviewedID.Value(), "reviewed_ids"))
|
||||
}
|
||||
if options.ReviewRequestedID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.ReviewRequestedID, "review_requested_ids"))
|
||||
if options.ReviewRequestedID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.ReviewRequestedID.Value(), "review_requested_ids"))
|
||||
}
|
||||
|
||||
if options.SubscriberID != nil {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(*options.SubscriberID, "subscriber_ids"))
|
||||
if options.SubscriberID.Has() {
|
||||
queries = append(queries, inner_bleve.NumericEqualityQuery(options.SubscriberID.Value(), "subscriber_ids"))
|
||||
}
|
||||
|
||||
if options.UpdatedAfterUnix != nil || options.UpdatedBeforeUnix != nil {
|
||||
queries = append(queries, inner_bleve.NumericRangeInclusiveQuery(options.UpdatedAfterUnix, options.UpdatedBeforeUnix, "updated_unix"))
|
||||
if options.UpdatedAfterUnix.Has() || options.UpdatedBeforeUnix.Has() {
|
||||
queries = append(queries, inner_bleve.NumericRangeInclusiveQuery(
|
||||
options.UpdatedAfterUnix,
|
||||
options.UpdatedBeforeUnix,
|
||||
"updated_unix"))
|
||||
}
|
||||
|
||||
var indexerQuery query.Query = bleve.NewConjunctionQuery(queries...)
|
||||
|
|
|
@ -15,22 +15,6 @@ import (
|
|||
)
|
||||
|
||||
func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_model.IssuesOptions, error) {
|
||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||
convertID := func(id *int64) int64 {
|
||||
if id == nil {
|
||||
return 0
|
||||
}
|
||||
if *id == 0 {
|
||||
return db.NoConditionID
|
||||
}
|
||||
return *id
|
||||
}
|
||||
convertInt64 := func(i *int64) int64 {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
return *i
|
||||
}
|
||||
var sortType string
|
||||
switch options.SortBy {
|
||||
case internal.SortByCreatedAsc:
|
||||
|
@ -53,6 +37,18 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
|
|||
sortType = "newest"
|
||||
}
|
||||
|
||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||
convertID := func(id optional.Option[int64]) int64 {
|
||||
if !id.Has() {
|
||||
return 0
|
||||
}
|
||||
value := id.Value()
|
||||
if value == 0 {
|
||||
return db.NoConditionID
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
opts := &issue_model.IssuesOptions{
|
||||
Paginator: options.Paginator,
|
||||
RepoIDs: options.RepoIDs,
|
||||
|
@ -73,8 +69,8 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
|
|||
IncludeMilestones: nil,
|
||||
SortType: sortType,
|
||||
IssueIDs: nil,
|
||||
UpdatedAfterUnix: convertInt64(options.UpdatedAfterUnix),
|
||||
UpdatedBeforeUnix: convertInt64(options.UpdatedBeforeUnix),
|
||||
UpdatedAfterUnix: options.UpdatedAfterUnix.Value(),
|
||||
UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(),
|
||||
PriorityRepoID: 0,
|
||||
IsArchived: optional.None[bool](),
|
||||
Org: nil,
|
||||
|
|
|
@ -6,6 +6,7 @@ package issues
|
|||
import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
)
|
||||
|
||||
func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions {
|
||||
|
@ -38,13 +39,12 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
|||
}
|
||||
|
||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||
convertID := func(id int64) *int64 {
|
||||
convertID := func(id int64) optional.Option[int64] {
|
||||
if id > 0 {
|
||||
return &id
|
||||
return optional.Some(id)
|
||||
}
|
||||
if id == db.NoConditionID {
|
||||
var zero int64
|
||||
return &zero
|
||||
return optional.None[int64]()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -59,10 +59,10 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
|||
searchOpt.SubscriberID = convertID(opts.SubscriberID)
|
||||
|
||||
if opts.UpdatedAfterUnix > 0 {
|
||||
searchOpt.UpdatedAfterUnix = &opts.UpdatedAfterUnix
|
||||
searchOpt.UpdatedAfterUnix = optional.Some(opts.UpdatedAfterUnix)
|
||||
}
|
||||
if opts.UpdatedBeforeUnix > 0 {
|
||||
searchOpt.UpdatedBeforeUnix = &opts.UpdatedBeforeUnix
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(opts.UpdatedBeforeUnix)
|
||||
}
|
||||
|
||||
searchOpt.Paginator = opts.Paginator
|
||||
|
|
|
@ -195,43 +195,43 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
query.Must(elastic.NewTermsQuery("milestone_id", toAnySlice(options.MilestoneIDs)...))
|
||||
}
|
||||
|
||||
if options.ProjectID != nil {
|
||||
query.Must(elastic.NewTermQuery("project_id", *options.ProjectID))
|
||||
if options.ProjectID.Has() {
|
||||
query.Must(elastic.NewTermQuery("project_id", options.ProjectID.Value()))
|
||||
}
|
||||
if options.ProjectBoardID != nil {
|
||||
query.Must(elastic.NewTermQuery("project_board_id", *options.ProjectBoardID))
|
||||
if options.ProjectBoardID.Has() {
|
||||
query.Must(elastic.NewTermQuery("project_board_id", options.ProjectBoardID.Value()))
|
||||
}
|
||||
|
||||
if options.PosterID != nil {
|
||||
query.Must(elastic.NewTermQuery("poster_id", *options.PosterID))
|
||||
if options.PosterID.Has() {
|
||||
query.Must(elastic.NewTermQuery("poster_id", options.PosterID.Value()))
|
||||
}
|
||||
|
||||
if options.AssigneeID != nil {
|
||||
query.Must(elastic.NewTermQuery("assignee_id", *options.AssigneeID))
|
||||
if options.AssigneeID.Has() {
|
||||
query.Must(elastic.NewTermQuery("assignee_id", options.AssigneeID.Value()))
|
||||
}
|
||||
|
||||
if options.MentionID != nil {
|
||||
query.Must(elastic.NewTermQuery("mention_ids", *options.MentionID))
|
||||
if options.MentionID.Has() {
|
||||
query.Must(elastic.NewTermQuery("mention_ids", options.MentionID.Value()))
|
||||
}
|
||||
|
||||
if options.ReviewedID != nil {
|
||||
query.Must(elastic.NewTermQuery("reviewed_ids", *options.ReviewedID))
|
||||
if options.ReviewedID.Has() {
|
||||
query.Must(elastic.NewTermQuery("reviewed_ids", options.ReviewedID.Value()))
|
||||
}
|
||||
if options.ReviewRequestedID != nil {
|
||||
query.Must(elastic.NewTermQuery("review_requested_ids", *options.ReviewRequestedID))
|
||||
if options.ReviewRequestedID.Has() {
|
||||
query.Must(elastic.NewTermQuery("review_requested_ids", options.ReviewRequestedID.Value()))
|
||||
}
|
||||
|
||||
if options.SubscriberID != nil {
|
||||
query.Must(elastic.NewTermQuery("subscriber_ids", *options.SubscriberID))
|
||||
if options.SubscriberID.Has() {
|
||||
query.Must(elastic.NewTermQuery("subscriber_ids", options.SubscriberID.Value()))
|
||||
}
|
||||
|
||||
if options.UpdatedAfterUnix != nil || options.UpdatedBeforeUnix != nil {
|
||||
if options.UpdatedAfterUnix.Has() || options.UpdatedBeforeUnix.Has() {
|
||||
q := elastic.NewRangeQuery("updated_unix")
|
||||
if options.UpdatedAfterUnix != nil {
|
||||
q.Gte(*options.UpdatedAfterUnix)
|
||||
if options.UpdatedAfterUnix.Has() {
|
||||
q.Gte(options.UpdatedAfterUnix.Value())
|
||||
}
|
||||
if options.UpdatedBeforeUnix != nil {
|
||||
q.Lte(*options.UpdatedBeforeUnix)
|
||||
if options.UpdatedBeforeUnix.Has() {
|
||||
q.Lte(options.UpdatedBeforeUnix.Value())
|
||||
}
|
||||
query.Must(q)
|
||||
}
|
||||
|
|
|
@ -134,63 +134,60 @@ func searchIssueInRepo(t *testing.T) {
|
|||
}
|
||||
|
||||
func searchIssueByID(t *testing.T) {
|
||||
int64Pointer := func(x int64) *int64 {
|
||||
return &x
|
||||
}
|
||||
tests := []struct {
|
||||
opts SearchOptions
|
||||
expectedIDs []int64
|
||||
}{
|
||||
{
|
||||
SearchOptions{
|
||||
PosterID: int64Pointer(1),
|
||||
opts: SearchOptions{
|
||||
PosterID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{11, 6, 3, 2, 1},
|
||||
expectedIDs: []int64{11, 6, 3, 2, 1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
AssigneeID: int64Pointer(1),
|
||||
opts: SearchOptions{
|
||||
AssigneeID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{6, 1},
|
||||
expectedIDs: []int64{6, 1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
MentionID: int64Pointer(4),
|
||||
opts: SearchOptions{
|
||||
MentionID: optional.Some(int64(4)),
|
||||
},
|
||||
[]int64{1},
|
||||
expectedIDs: []int64{1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
ReviewedID: int64Pointer(1),
|
||||
opts: SearchOptions{
|
||||
ReviewedID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{},
|
||||
expectedIDs: []int64{},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
ReviewRequestedID: int64Pointer(1),
|
||||
opts: SearchOptions{
|
||||
ReviewRequestedID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{12},
|
||||
expectedIDs: []int64{12},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
SubscriberID: int64Pointer(1),
|
||||
opts: SearchOptions{
|
||||
SubscriberID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{11, 6, 5, 3, 2, 1},
|
||||
expectedIDs: []int64{11, 6, 5, 3, 2, 1},
|
||||
},
|
||||
{
|
||||
// issue 20 request user 15 and team 5 which user 15 belongs to
|
||||
// the review request number of issue 20 should be 1
|
||||
SearchOptions{
|
||||
ReviewRequestedID: int64Pointer(15),
|
||||
opts: SearchOptions{
|
||||
ReviewRequestedID: optional.Some(int64(15)),
|
||||
},
|
||||
[]int64{12, 20},
|
||||
expectedIDs: []int64{12, 20},
|
||||
},
|
||||
{
|
||||
// user 20 approved the issue 20, so return nothing
|
||||
SearchOptions{
|
||||
ReviewRequestedID: int64Pointer(20),
|
||||
opts: SearchOptions{
|
||||
ReviewRequestedID: optional.Some(int64(20)),
|
||||
},
|
||||
[]int64{},
|
||||
expectedIDs: []int64{},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -318,16 +315,13 @@ func searchIssueByLabelID(t *testing.T) {
|
|||
}
|
||||
|
||||
func searchIssueByTime(t *testing.T) {
|
||||
int64Pointer := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
tests := []struct {
|
||||
opts SearchOptions
|
||||
expectedIDs []int64
|
||||
}{
|
||||
{
|
||||
SearchOptions{
|
||||
UpdatedAfterUnix: int64Pointer(0),
|
||||
UpdatedAfterUnix: optional.Some(int64(0)),
|
||||
},
|
||||
[]int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
|
||||
},
|
||||
|
@ -363,28 +357,25 @@ func searchIssueWithOrder(t *testing.T) {
|
|||
}
|
||||
|
||||
func searchIssueInProject(t *testing.T) {
|
||||
int64Pointer := func(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
tests := []struct {
|
||||
opts SearchOptions
|
||||
expectedIDs []int64
|
||||
}{
|
||||
{
|
||||
SearchOptions{
|
||||
ProjectID: int64Pointer(1),
|
||||
ProjectID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{5, 3, 2, 1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
ProjectBoardID: int64Pointer(1),
|
||||
ProjectBoardID: optional.Some(int64(1)),
|
||||
},
|
||||
[]int64{1},
|
||||
},
|
||||
{
|
||||
SearchOptions{
|
||||
ProjectBoardID: int64Pointer(0), // issue with in default board
|
||||
ProjectBoardID: optional.Some(int64(0)), // issue with in default board
|
||||
},
|
||||
[]int64{2},
|
||||
},
|
||||
|
|
|
@ -89,22 +89,22 @@ type SearchOptions struct {
|
|||
|
||||
MilestoneIDs []int64 // milestones the issues have
|
||||
|
||||
ProjectID *int64 // project the issues belong to
|
||||
ProjectBoardID *int64 // project board the issues belong to
|
||||
ProjectID optional.Option[int64] // project the issues belong to
|
||||
ProjectBoardID optional.Option[int64] // project board the issues belong to
|
||||
|
||||
PosterID *int64 // poster of the issues
|
||||
PosterID optional.Option[int64] // poster of the issues
|
||||
|
||||
AssigneeID *int64 // assignee of the issues, zero means no assignee
|
||||
AssigneeID optional.Option[int64] // assignee of the issues, zero means no assignee
|
||||
|
||||
MentionID *int64 // mentioned user of the issues
|
||||
MentionID optional.Option[int64] // mentioned user of the issues
|
||||
|
||||
ReviewedID *int64 // reviewer of the issues
|
||||
ReviewRequestedID *int64 // requested reviewer of the issues
|
||||
ReviewedID optional.Option[int64] // reviewer of the issues
|
||||
ReviewRequestedID optional.Option[int64] // requested reviewer of the issues
|
||||
|
||||
SubscriberID *int64 // subscriber of the issues
|
||||
SubscriberID optional.Option[int64] // subscriber of the issues
|
||||
|
||||
UpdatedAfterUnix *int64
|
||||
UpdatedBeforeUnix *int64
|
||||
UpdatedAfterUnix optional.Option[int64]
|
||||
UpdatedBeforeUnix optional.Option[int64]
|
||||
|
||||
db.Paginator
|
||||
|
||||
|
|
|
@ -300,10 +300,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ProjectID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
ProjectID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -321,10 +318,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ProjectID: func() *int64 {
|
||||
id := int64(0)
|
||||
return &id
|
||||
}(),
|
||||
ProjectID: optional.Some(int64(0)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -342,10 +336,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ProjectBoardID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
ProjectBoardID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -363,10 +354,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ProjectBoardID: func() *int64 {
|
||||
id := int64(0)
|
||||
return &id
|
||||
}(),
|
||||
ProjectBoardID: optional.Some(int64(0)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -384,10 +372,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
PosterID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
PosterID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -405,10 +390,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
AssigneeID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
AssigneeID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -426,10 +408,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
AssigneeID: func() *int64 {
|
||||
id := int64(0)
|
||||
return &id
|
||||
}(),
|
||||
AssigneeID: optional.Some(int64(0)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -447,10 +426,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
MentionID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
MentionID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -468,10 +444,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ReviewedID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
ReviewedID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -489,10 +462,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
ReviewRequestedID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
ReviewRequestedID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -510,10 +480,7 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
SubscriberID: func() *int64 {
|
||||
id := int64(1)
|
||||
return &id
|
||||
}(),
|
||||
SubscriberID: optional.Some(int64(1)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
@ -531,14 +498,8 @@ var cases = []*testIndexerCase{
|
|||
Paginator: &db.ListOptions{
|
||||
PageSize: 5,
|
||||
},
|
||||
UpdatedAfterUnix: func() *int64 {
|
||||
var t int64 = 20
|
||||
return &t
|
||||
}(),
|
||||
UpdatedBeforeUnix: func() *int64 {
|
||||
var t int64 = 30
|
||||
return &t
|
||||
}(),
|
||||
UpdatedAfterUnix: optional.Some(int64(20)),
|
||||
UpdatedBeforeUnix: optional.Some(int64(30)),
|
||||
},
|
||||
Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) {
|
||||
assert.Equal(t, 5, len(result.Hits))
|
||||
|
|
|
@ -170,41 +170,41 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
query.And(inner_meilisearch.NewFilterIn("milestone_id", options.MilestoneIDs...))
|
||||
}
|
||||
|
||||
if options.ProjectID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("project_id", *options.ProjectID))
|
||||
if options.ProjectID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("project_id", options.ProjectID.Value()))
|
||||
}
|
||||
if options.ProjectBoardID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("project_board_id", *options.ProjectBoardID))
|
||||
if options.ProjectBoardID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("project_board_id", options.ProjectBoardID.Value()))
|
||||
}
|
||||
|
||||
if options.PosterID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("poster_id", *options.PosterID))
|
||||
if options.PosterID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("poster_id", options.PosterID.Value()))
|
||||
}
|
||||
|
||||
if options.AssigneeID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("assignee_id", *options.AssigneeID))
|
||||
if options.AssigneeID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("assignee_id", options.AssigneeID.Value()))
|
||||
}
|
||||
|
||||
if options.MentionID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("mention_ids", *options.MentionID))
|
||||
if options.MentionID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("mention_ids", options.MentionID.Value()))
|
||||
}
|
||||
|
||||
if options.ReviewedID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("reviewed_ids", *options.ReviewedID))
|
||||
if options.ReviewedID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("reviewed_ids", options.ReviewedID.Value()))
|
||||
}
|
||||
if options.ReviewRequestedID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("review_requested_ids", *options.ReviewRequestedID))
|
||||
if options.ReviewRequestedID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("review_requested_ids", options.ReviewRequestedID.Value()))
|
||||
}
|
||||
|
||||
if options.SubscriberID != nil {
|
||||
query.And(inner_meilisearch.NewFilterEq("subscriber_ids", *options.SubscriberID))
|
||||
if options.SubscriberID.Has() {
|
||||
query.And(inner_meilisearch.NewFilterEq("subscriber_ids", options.SubscriberID.Value()))
|
||||
}
|
||||
|
||||
if options.UpdatedAfterUnix != nil {
|
||||
query.And(inner_meilisearch.NewFilterGte("updated_unix", *options.UpdatedAfterUnix))
|
||||
if options.UpdatedAfterUnix.Has() {
|
||||
query.And(inner_meilisearch.NewFilterGte("updated_unix", options.UpdatedAfterUnix.Value()))
|
||||
}
|
||||
if options.UpdatedBeforeUnix != nil {
|
||||
query.And(inner_meilisearch.NewFilterLte("updated_unix", *options.UpdatedBeforeUnix))
|
||||
if options.UpdatedBeforeUnix.Has() {
|
||||
query.And(inner_meilisearch.NewFilterLte("updated_unix", options.UpdatedBeforeUnix.Value()))
|
||||
}
|
||||
|
||||
if options.SortBy == "" {
|
||||
|
|
|
@ -609,7 +609,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||
if ok && strings.Contains(mention, "/") {
|
||||
mentionOrgAndTeam := strings.Split(mention, "/")
|
||||
if mentionOrgAndTeam[0][1:] == ctx.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") {
|
||||
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention"))
|
||||
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), "org", ctx.Metas["org"], "teams", mentionOrgAndTeam[1]), mention, "mention"))
|
||||
node = node.NextSibling.NextSibling
|
||||
start = 0
|
||||
continue
|
||||
|
@ -620,7 +620,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
|||
mentionedUsername := mention[1:]
|
||||
|
||||
if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
|
||||
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(setting.AppURL, mentionedUsername), mention, "mention"))
|
||||
replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
|
||||
node = node.NextSibling.NextSibling
|
||||
} else {
|
||||
node = node.NextSibling
|
||||
|
@ -898,9 +898,9 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
path = "pulls"
|
||||
}
|
||||
if ref.Owner == "" {
|
||||
link = createLink(util.URLJoin(setting.AppURL, ctx.Metas["user"], ctx.Metas["repo"], path, ref.Issue), reftext, "ref-issue")
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], path, ref.Issue), reftext, "ref-issue")
|
||||
} else {
|
||||
link = createLink(util.URLJoin(setting.AppURL, ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -939,7 +939,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
}
|
||||
|
||||
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
|
||||
link := createLink(util.URLJoin(setting.AppSubURL, ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
|
||||
link := createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, "commit", ref.CommitSha), reftext, "commit")
|
||||
|
||||
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
||||
node = node.NextSibling.NextSibling
|
||||
|
@ -1166,7 +1166,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
continue
|
||||
}
|
||||
|
||||
link := util.URLJoin(setting.AppURL, ctx.Metas["user"], ctx.Metas["repo"], "commit", hash)
|
||||
link := util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], "commit", hash)
|
||||
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
|
||||
start = 0
|
||||
node = node.NextSibling.NextSibling
|
||||
|
|
|
@ -287,6 +287,7 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
|
|||
}
|
||||
|
||||
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
|
||||
ctx.Links.AbsolutePrefix = true
|
||||
if ctx.Links.Base == "" {
|
||||
ctx.Links.Base = TestRepoURL
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ func TestRender_Commits(t *testing.T) {
|
|||
Ctx: git.DefaultContext,
|
||||
RelativePath: ".md",
|
||||
Links: markup.Links{
|
||||
Base: markup.TestRepoURL,
|
||||
AbsolutePrefix: true,
|
||||
Base: markup.TestRepoURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, input)
|
||||
|
@ -96,7 +97,8 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||
Ctx: git.DefaultContext,
|
||||
RelativePath: "a.md",
|
||||
Links: markup.Links{
|
||||
Base: setting.AppSubURL,
|
||||
AbsolutePrefix: true,
|
||||
Base: setting.AppSubURL,
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, input)
|
||||
|
@ -588,7 +590,8 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
|||
err := markup.PostProcess(&markup.RenderContext{
|
||||
Ctx: git.DefaultContext,
|
||||
Links: markup.Links{
|
||||
Base: "https://example.com",
|
||||
AbsolutePrefix: true,
|
||||
Base: "https://example.com",
|
||||
},
|
||||
Metas: localMetas,
|
||||
}, strings.NewReader(input), &res)
|
||||
|
|
|
@ -130,11 +130,11 @@ func testAnswers(baseURLContent, baseURLImages string) []string {
|
|||
<li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||
<li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
|
||||
</ul>
|
||||
<p>See commit <a href="http://localhost:3000/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
||||
<p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
|
||||
<p>Ideas and codes</p>
|
||||
<ul>
|
||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li>
|
||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li>
|
||||
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li>
|
||||
<li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li>
|
||||
<li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
|
||||
<li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
|
||||
<li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
|
||||
|
|
|
@ -82,9 +82,17 @@ type RenderContext struct {
|
|||
}
|
||||
|
||||
type Links struct {
|
||||
Base string
|
||||
BranchPath string
|
||||
TreePath string
|
||||
AbsolutePrefix bool
|
||||
Base string
|
||||
BranchPath string
|
||||
TreePath string
|
||||
}
|
||||
|
||||
func (l *Links) Prefix() string {
|
||||
if l.AbsolutePrefix {
|
||||
return setting.AppURL
|
||||
}
|
||||
return setting.AppSubURL
|
||||
}
|
||||
|
||||
func (l *Links) HasBranchInfo() bool {
|
||||
|
|
|
@ -38,7 +38,7 @@ func NewFuncMap() template.FuncMap {
|
|||
"SafeHTML": SafeHTML,
|
||||
"HTMLFormat": HTMLFormat,
|
||||
"HTMLEscape": HTMLEscape,
|
||||
"QueryEscape": url.QueryEscape,
|
||||
"QueryEscape": QueryEscape,
|
||||
"JSEscape": JSEscapeSafe,
|
||||
"SanitizeHTML": SanitizeHTML,
|
||||
"URLJoin": util.URLJoin,
|
||||
|
@ -226,6 +226,10 @@ func JSEscapeSafe(s string) template.HTML {
|
|||
return template.HTML(template.JSEscapeString(s))
|
||||
}
|
||||
|
||||
func QueryEscape(s string) template.URL {
|
||||
return template.URL(url.QueryEscape(s))
|
||||
}
|
||||
|
||||
// DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
|
||||
func DotEscape(raw string) string {
|
||||
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
|
@ -118,10 +119,14 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string)
|
|||
}
|
||||
|
||||
// RenderLabel renders a label
|
||||
func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
||||
labelScope := label.ExclusiveScope()
|
||||
// locale is needed due to an import cycle with our context providing the `Tr` function
|
||||
func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
||||
var (
|
||||
archivedCSSClass string
|
||||
textColor = "#111"
|
||||
labelScope = label.ExclusiveScope()
|
||||
)
|
||||
|
||||
textColor := "#111"
|
||||
r, g, b := util.HexToRBGColor(label.Color)
|
||||
// Determine if label text should be light or dark to be readable on background color
|
||||
if util.UseLightTextOnBackground(r, g, b) {
|
||||
|
@ -130,10 +135,15 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
|||
|
||||
description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
|
||||
|
||||
if label.IsArchived() {
|
||||
archivedCSSClass = "archived-label"
|
||||
description = fmt.Sprintf("(%s) %s", locale.TrString("archived"), description)
|
||||
}
|
||||
|
||||
if labelScope == "" {
|
||||
// Regular label
|
||||
s := fmt.Sprintf("<div class='ui label' style='color: %s !important; background-color: %s !important' data-tooltip-content title='%s'>%s</div>",
|
||||
textColor, label.Color, description, RenderEmoji(ctx, label.Name))
|
||||
s := fmt.Sprintf("<div class='ui label %s' style='color: %s !important; background-color: %s !important;' data-tooltip-content title='%s'>%s</div>",
|
||||
archivedCSSClass, textColor, label.Color, description, RenderEmoji(ctx, label.Name))
|
||||
return template.HTML(s)
|
||||
}
|
||||
|
||||
|
@ -166,11 +176,11 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
|
|||
itemColor := "#" + hex.EncodeToString(itemBytes)
|
||||
scopeColor := "#" + hex.EncodeToString(scopeBytes)
|
||||
|
||||
s := fmt.Sprintf("<span class='ui label scope-parent' data-tooltip-content title='%s'>"+
|
||||
s := fmt.Sprintf("<span class='ui label %s scope-parent' data-tooltip-content title='%s'>"+
|
||||
"<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
||||
"<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+
|
||||
"</span>",
|
||||
description,
|
||||
archivedCSSClass, description,
|
||||
textColor, scopeColor, scopeText,
|
||||
textColor, itemColor, itemText)
|
||||
return template.HTML(s)
|
||||
|
@ -211,7 +221,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
|
|||
return output
|
||||
}
|
||||
|
||||
func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML {
|
||||
func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string) template.HTML {
|
||||
htmlCode := `<span class="labels-list">`
|
||||
for _, label := range labels {
|
||||
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
|
||||
|
@ -219,7 +229,7 @@ func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink st
|
|||
continue
|
||||
}
|
||||
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ",
|
||||
repoLink, label.ID, RenderLabel(ctx, label))
|
||||
repoLink, label.ID, RenderLabel(ctx, locale, label))
|
||||
}
|
||||
htmlCode += "</span>"
|
||||
return template.HTML(htmlCode)
|
||||
|
|
|
@ -117,21 +117,21 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a582
|
|||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
||||
<a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> test
|
||||
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
<a href="/mention-user" class="mention">@mention-user</a> test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space`
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessage(t *testing.T) {
|
||||
expected := `space <a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> `
|
||||
expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
||||
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="http://localhost:3000/mention-user" class="mention">@mention-user</a>`
|
||||
expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="/mention-user" class="mention">@mention-user</a>`
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
|
||||
}
|
||||
|
@ -155,14 +155,14 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
mail@domain.com
|
||||
@mention-user test
|
||||
<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space
|
||||
`
|
||||
assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
|
||||
}
|
||||
|
||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
expected := `<p>space <a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a><br/>
|
||||
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
||||
/just/a/path.bin
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||
<a href="/file.bin" rel="nofollow">local link</a>
|
||||
|
@ -179,7 +179,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a582
|
|||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
||||
<a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a> test
|
||||
<a href="/mention-user" rel="nofollow">@mention-user</a> test
|
||||
#123
|
||||
space</p>
|
||||
`
|
||||
|
|
|
@ -51,18 +51,16 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
|||
|
||||
attrs := make([]string, 0, 10+len(extraAttrs))
|
||||
attrs = append(attrs, extraAttrs...)
|
||||
attrs = append(attrs, `data-tooltip-content`, `data-tooltip-interactive="true"`)
|
||||
attrs = append(attrs, `format="datetime"`, `weekday=""`, `year="numeric"`)
|
||||
attrs = append(attrs, `weekday=""`, `year="numeric"`)
|
||||
|
||||
switch format {
|
||||
case "short":
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`)
|
||||
case "long":
|
||||
attrs = append(attrs, `month="long"`, `day="numeric"`)
|
||||
case "full":
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`)
|
||||
case "short", "long": // date only
|
||||
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
|
||||
return template.HTML(fmt.Sprintf(`<gitea-absolute-date %s date="%s">%s</gitea-absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
case "full": // full date including time
|
||||
attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
|
||||
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported format %s", format))
|
||||
}
|
||||
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ func TestDateTime(t *testing.T) {
|
|||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||
|
||||
refTimeStr := "2018-01-01T00:00:00Z"
|
||||
refDateStr := "2018-01-01"
|
||||
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
||||
refTimeStamp := TimeStamp(refTime.Unix())
|
||||
|
||||
|
@ -27,17 +28,20 @@ func TestDateTime(t *testing.T) {
|
|||
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0)))
|
||||
|
||||
actual := DateTime("short", "invalid")
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="invalid">invalid</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTimeStr)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTime)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2018-01-01T00:00:00Z">2018-01-01</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refDateStr)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<gitea-absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</gitea-absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("full", refTimeStamp)
|
||||
assert.EqualValues(t, `<relative-time data-tooltip-content data-tooltip-interactive="true" format="datetime" weekday="" year="numeric" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||
assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||
}
|
||||
|
|
|
@ -53,3 +53,12 @@ func Sorted[S ~[]E, E cmp.Ordered](values S) S {
|
|||
slices.Sort(values)
|
||||
return values
|
||||
}
|
||||
|
||||
// TODO: Replace with "maps.Values" once available
|
||||
func ValuesOfMap[K comparable, V any](m map[K]V) []V {
|
||||
values := make([]V, 0, len(m))
|
||||
for _, v := range m {
|
||||
values = append(values, v)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
|
|
@ -804,7 +804,6 @@ gpg_invalid_token_signature = The provided GPG key, signature and token do not m
|
|||
gpg_token_required = You must provide a signature for the below token
|
||||
gpg_token = Token
|
||||
gpg_token_help = You can generate a signature using:
|
||||
gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig
|
||||
gpg_token_signature = Armored GPG signature
|
||||
key_signature_gpg_placeholder = Begins with '-----BEGIN PGP SIGNATURE-----'
|
||||
verify_gpg_key_success = GPG key "%s" has been verified.
|
||||
|
|
|
@ -269,28 +269,28 @@ func SearchIssues(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
if since != 0 {
|
||||
searchOpt.UpdatedAfterUnix = &since
|
||||
searchOpt.UpdatedAfterUnix = optional.Some(since)
|
||||
}
|
||||
if before != 0 {
|
||||
searchOpt.UpdatedBeforeUnix = &before
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctxUserID := ctx.Doer.ID
|
||||
if ctx.FormBool("created") {
|
||||
searchOpt.PosterID = &ctxUserID
|
||||
searchOpt.PosterID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("assigned") {
|
||||
searchOpt.AssigneeID = &ctxUserID
|
||||
searchOpt.AssigneeID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("mentioned") {
|
||||
searchOpt.MentionID = &ctxUserID
|
||||
searchOpt.MentionID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("review_requested") {
|
||||
searchOpt.ReviewRequestedID = &ctxUserID
|
||||
searchOpt.ReviewRequestedID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("reviewed") {
|
||||
searchOpt.ReviewedID = &ctxUserID
|
||||
searchOpt.ReviewedID = optional.Some(ctxUserID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -502,10 +502,10 @@ func ListIssues(ctx *context.APIContext) {
|
|||
SortBy: issue_indexer.SortByCreatedDesc,
|
||||
}
|
||||
if since != 0 {
|
||||
searchOpt.UpdatedAfterUnix = &since
|
||||
searchOpt.UpdatedAfterUnix = optional.Some(since)
|
||||
}
|
||||
if before != 0 {
|
||||
searchOpt.UpdatedBeforeUnix = &before
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
if len(labelIDs) == 1 && labelIDs[0] == 0 {
|
||||
searchOpt.NoLabelOnly = true
|
||||
|
@ -526,13 +526,13 @@ func ListIssues(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
if createdByID > 0 {
|
||||
searchOpt.PosterID = &createdByID
|
||||
searchOpt.PosterID = optional.Some(createdByID)
|
||||
}
|
||||
if assignedByID > 0 {
|
||||
searchOpt.AssigneeID = &assignedByID
|
||||
searchOpt.AssigneeID = optional.Some(assignedByID)
|
||||
}
|
||||
if mentionedByID > 0 {
|
||||
searchOpt.MentionID = &mentionedByID
|
||||
searchOpt.MentionID = optional.Some(mentionedByID)
|
||||
}
|
||||
|
||||
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
|
||||
|
|
|
@ -323,10 +323,6 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "LoadIssues", err)
|
||||
return
|
||||
}
|
||||
if err := comments.LoadPosters(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
|
||||
return
|
||||
}
|
||||
if err := comments.LoadAttachments(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
|
||||
return
|
||||
|
|
|
@ -34,7 +34,8 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: urlPrefix,
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
},
|
||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
|
@ -79,7 +80,8 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||
if err := markup.Render(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: urlPrefix,
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
},
|
||||
Metas: meta,
|
||||
IsWiki: wiki,
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
tplFork base.TplName = "repo/pulls/fork"
|
||||
)
|
||||
|
||||
func getForkRepository(ctx *context.Context) *repo_model.Repository {
|
||||
forkRepo := ctx.Repo.Repository
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if forkRepo.IsEmpty {
|
||||
log.Trace("Empty repository %-v", forkRepo)
|
||||
ctx.NotFound("getForkRepository", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := forkRepo.LoadOwner(ctx); err != nil {
|
||||
ctx.ServerError("LoadOwner", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.Data["repo_name"] = forkRepo.Name
|
||||
ctx.Data["description"] = forkRepo.Description
|
||||
ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
|
||||
canForkToUser := forkRepo.OwnerID != ctx.Doer.ID && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID)
|
||||
|
||||
ctx.Data["ForkRepo"] = forkRepo
|
||||
|
||||
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
|
||||
return nil
|
||||
}
|
||||
var orgs []*organization.Organization
|
||||
for _, org := range ownedOrgs {
|
||||
if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, forkRepo.ID) {
|
||||
orgs = append(orgs, org)
|
||||
}
|
||||
}
|
||||
|
||||
traverseParentRepo := forkRepo
|
||||
for {
|
||||
if ctx.Doer.ID == traverseParentRepo.OwnerID {
|
||||
canForkToUser = false
|
||||
} else {
|
||||
for i, org := range orgs {
|
||||
if org.ID == traverseParentRepo.OwnerID {
|
||||
orgs = append(orgs[:i], orgs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !traverseParentRepo.IsFork {
|
||||
break
|
||||
}
|
||||
traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CanForkToUser"] = canForkToUser
|
||||
ctx.Data["Orgs"] = orgs
|
||||
|
||||
if canForkToUser {
|
||||
ctx.Data["ContextUser"] = ctx.Doer
|
||||
} else if len(orgs) > 0 {
|
||||
ctx.Data["ContextUser"] = orgs[0]
|
||||
} else {
|
||||
ctx.Data["CanForkRepo"] = false
|
||||
ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true)
|
||||
return nil
|
||||
}
|
||||
|
||||
branches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
// Add it as the first option
|
||||
ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindBranchNames", err)
|
||||
return nil
|
||||
}
|
||||
ctx.Data["Branches"] = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
||||
|
||||
return forkRepo
|
||||
}
|
||||
|
||||
// Fork render repository fork page
|
||||
func Fork(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
|
||||
if ctx.Doer.CanForkRepo() {
|
||||
ctx.Data["CanForkRepo"] = true
|
||||
} else {
|
||||
maxCreationLimit := ctx.Doer.MaxCreationLimit()
|
||||
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
|
||||
ctx.Flash.Error(msg, true)
|
||||
}
|
||||
|
||||
getForkRepository(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplFork)
|
||||
}
|
||||
|
||||
// ForkPost response for forking a repository
|
||||
func ForkPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateRepoForm)
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
ctx.Data["CanForkRepo"] = true
|
||||
|
||||
ctxUser := checkContextUser(ctx, form.UID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
forkRepo := getForkRepository(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplFork)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
traverseParentRepo := forkRepo
|
||||
for {
|
||||
if ctxUser.ID == traverseParentRepo.OwnerID {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
|
||||
return
|
||||
}
|
||||
repo := repo_model.GetForkedRepo(ctx, ctxUser.ID, traverseParentRepo.ID)
|
||||
if repo != nil {
|
||||
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
|
||||
return
|
||||
}
|
||||
if !traverseParentRepo.IsFork {
|
||||
break
|
||||
}
|
||||
traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is allowed to create repo's on the organization.
|
||||
if ctxUser.IsOrganization() {
|
||||
isAllowedToFork, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("CanCreateOrgRepo", err)
|
||||
return
|
||||
} else if !isAllowedToFork {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := repo_service.ForkRepository(ctx, ctx.Doer, ctxUser, repo_service.ForkRepoOptions{
|
||||
BaseRepo: forkRepo,
|
||||
Name: form.RepoName,
|
||||
Description: form.Description,
|
||||
SingleBranch: form.ForkSingleBranch,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Data["Err_RepoName"] = true
|
||||
switch {
|
||||
case repo_model.IsErrReachLimitOfRepo(err):
|
||||
maxCreationLimit := ctxUser.MaxCreationLimit()
|
||||
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
|
||||
ctx.RenderWithErr(msg, tplFork, &form)
|
||||
case repo_model.IsErrRepoAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
|
||||
case repo_model.IsErrRepoFilesAlreadyExist(err):
|
||||
switch {
|
||||
case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplFork, form)
|
||||
case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplFork, form)
|
||||
case setting.Repository.AllowDeleteOfUnadoptedRepositories:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplFork, form)
|
||||
default:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplFork, form)
|
||||
}
|
||||
case db.IsErrNameReserved(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
|
||||
case errors.Is(err, user_model.ErrBlockedUser):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.fork.blocked_user"), tplFork, form)
|
||||
default:
|
||||
ctx.ServerError("ForkPost", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
|
||||
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
|
||||
}
|
|
@ -324,15 +324,15 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
|||
return
|
||||
}
|
||||
|
||||
// Get posters.
|
||||
for i := range issues {
|
||||
// Check read status
|
||||
if !ctx.IsSigned {
|
||||
issues[i].IsRead = true
|
||||
} else if err = issues[i].GetIsRead(ctx, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("GetIsRead", err)
|
||||
if ctx.IsSigned {
|
||||
if err := issues.LoadIsRead(ctx, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("LoadIsRead", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for i := range issues {
|
||||
issues[i].IsRead = true
|
||||
}
|
||||
}
|
||||
|
||||
commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(ctx, issues)
|
||||
|
@ -1604,20 +1604,20 @@ func ViewIssue(ctx *context.Context) {
|
|||
|
||||
// Render comments and and fetch participants.
|
||||
participants[0] = issue.Poster
|
||||
|
||||
if err := issue.Comments.LoadAttachmentsByIssue(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttachmentsByIssue", err)
|
||||
return
|
||||
}
|
||||
if err := issue.Comments.LoadPosters(ctx); err != nil {
|
||||
ctx.ServerError("LoadPosters", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, comment = range issue.Comments {
|
||||
comment.Issue = issue
|
||||
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
ctx.ServerError("LoadPoster", err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview {
|
||||
if err := comment.LoadAttachments(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttachments", err)
|
||||
return
|
||||
}
|
||||
|
||||
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
|
||||
Links: markup.Links{
|
||||
Base: ctx.Repo.RepoLink,
|
||||
|
@ -1665,7 +1665,6 @@ func ViewIssue(ctx *context.Context) {
|
|||
comment.Milestone = ghostMilestone
|
||||
}
|
||||
} else if comment.Type == issues_model.CommentTypeProject {
|
||||
|
||||
if err = comment.LoadProject(ctx); err != nil {
|
||||
ctx.ServerError("LoadProject", err)
|
||||
return
|
||||
|
@ -1731,10 +1730,6 @@ func ViewIssue(ctx *context.Context) {
|
|||
for _, codeComments := range comment.Review.CodeComments {
|
||||
for _, lineComments := range codeComments {
|
||||
for _, c := range lineComments {
|
||||
if err := c.LoadAttachments(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttachments", err)
|
||||
return
|
||||
}
|
||||
// Check tag.
|
||||
role, ok = marked[c.PosterID]
|
||||
if ok {
|
||||
|
@ -2639,9 +2634,9 @@ func SearchIssues(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
var projectID *int64
|
||||
projectID := optional.None[int64]()
|
||||
if v := ctx.FormInt64("project"); v > 0 {
|
||||
projectID = &v
|
||||
projectID = optional.Some(v)
|
||||
}
|
||||
|
||||
// this api is also used in UI,
|
||||
|
@ -2670,28 +2665,28 @@ func SearchIssues(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if since != 0 {
|
||||
searchOpt.UpdatedAfterUnix = &since
|
||||
searchOpt.UpdatedAfterUnix = optional.Some(since)
|
||||
}
|
||||
if before != 0 {
|
||||
searchOpt.UpdatedBeforeUnix = &before
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctxUserID := ctx.Doer.ID
|
||||
if ctx.FormBool("created") {
|
||||
searchOpt.PosterID = &ctxUserID
|
||||
searchOpt.PosterID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("assigned") {
|
||||
searchOpt.AssigneeID = &ctxUserID
|
||||
searchOpt.AssigneeID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("mentioned") {
|
||||
searchOpt.MentionID = &ctxUserID
|
||||
searchOpt.MentionID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("review_requested") {
|
||||
searchOpt.ReviewRequestedID = &ctxUserID
|
||||
searchOpt.ReviewRequestedID = optional.Some(ctxUserID)
|
||||
}
|
||||
if ctx.FormBool("reviewed") {
|
||||
searchOpt.ReviewedID = &ctxUserID
|
||||
searchOpt.ReviewedID = optional.Some(ctxUserID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2796,9 +2791,9 @@ func ListIssues(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
var projectID *int64
|
||||
projectID := optional.None[int64]()
|
||||
if v := ctx.FormInt64("project"); v > 0 {
|
||||
projectID = &v
|
||||
projectID = optional.Some(v)
|
||||
}
|
||||
|
||||
isPull := optional.None[bool]()
|
||||
|
@ -2836,10 +2831,10 @@ func ListIssues(ctx *context.Context) {
|
|||
SortBy: issue_indexer.SortByCreatedDesc,
|
||||
}
|
||||
if since != 0 {
|
||||
searchOpt.UpdatedAfterUnix = &since
|
||||
searchOpt.UpdatedAfterUnix = optional.Some(since)
|
||||
}
|
||||
if before != 0 {
|
||||
searchOpt.UpdatedBeforeUnix = &before
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
if len(labelIDs) == 1 && labelIDs[0] == 0 {
|
||||
searchOpt.NoLabelOnly = true
|
||||
|
@ -2860,13 +2855,13 @@ func ListIssues(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if createdByID > 0 {
|
||||
searchOpt.PosterID = &createdByID
|
||||
searchOpt.PosterID = optional.Some(createdByID)
|
||||
}
|
||||
if assignedByID > 0 {
|
||||
searchOpt.AssigneeID = &assignedByID
|
||||
searchOpt.AssigneeID = optional.Some(assignedByID)
|
||||
}
|
||||
if mentionedByID > 0 {
|
||||
searchOpt.MentionID = &mentionedByID
|
||||
searchOpt.MentionID = optional.Some(mentionedByID)
|
||||
}
|
||||
|
||||
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"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/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -112,12 +111,11 @@ func NewLabel(ctx *context.Context) {
|
|||
}
|
||||
|
||||
l := &issues_model.Label{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: form.Title,
|
||||
Exclusive: form.Exclusive,
|
||||
Description: form.Description,
|
||||
Color: form.Color,
|
||||
ArchivedUnix: timeutil.TimeStamp(0),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Name: form.Title,
|
||||
Exclusive: form.Exclusive,
|
||||
Description: form.Description,
|
||||
Color: form.Color,
|
||||
}
|
||||
if err := issues_model.NewLabel(ctx, l); err != nil {
|
||||
ctx.ServerError("NewLabel", err)
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -20,7 +19,6 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -32,9 +30,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
issue_template "code.gitea.io/gitea/modules/issue/template"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
|
@ -53,7 +49,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
tplFork base.TplName = "repo/pulls/fork"
|
||||
tplCompareDiff base.TplName = "repo/diff/compare"
|
||||
tplPullCommits base.TplName = "repo/pulls/commits"
|
||||
tplPullFiles base.TplName = "repo/pulls/files"
|
||||
|
@ -112,215 +107,6 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
|
|||
return repo
|
||||
}
|
||||
|
||||
func getForkRepository(ctx *context.Context) *repo_model.Repository {
|
||||
forkRepo := ctx.Repo.Repository
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if forkRepo.IsEmpty {
|
||||
log.Trace("Empty repository %-v", forkRepo)
|
||||
ctx.NotFound("getForkRepository", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := forkRepo.LoadOwner(ctx); err != nil {
|
||||
ctx.ServerError("LoadOwner", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.Data["repo_name"] = forkRepo.Name
|
||||
ctx.Data["description"] = forkRepo.Description
|
||||
ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
|
||||
canForkToUser := forkRepo.OwnerID != ctx.Doer.ID && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID)
|
||||
|
||||
ctx.Data["ForkRepo"] = forkRepo
|
||||
|
||||
ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
|
||||
return nil
|
||||
}
|
||||
var orgs []*organization.Organization
|
||||
for _, org := range ownedOrgs {
|
||||
if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, forkRepo.ID) {
|
||||
orgs = append(orgs, org)
|
||||
}
|
||||
}
|
||||
|
||||
traverseParentRepo := forkRepo
|
||||
for {
|
||||
if ctx.Doer.ID == traverseParentRepo.OwnerID {
|
||||
canForkToUser = false
|
||||
} else {
|
||||
for i, org := range orgs {
|
||||
if org.ID == traverseParentRepo.OwnerID {
|
||||
orgs = append(orgs[:i], orgs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !traverseParentRepo.IsFork {
|
||||
break
|
||||
}
|
||||
traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CanForkToUser"] = canForkToUser
|
||||
ctx.Data["Orgs"] = orgs
|
||||
|
||||
if canForkToUser {
|
||||
ctx.Data["ContextUser"] = ctx.Doer
|
||||
} else if len(orgs) > 0 {
|
||||
ctx.Data["ContextUser"] = orgs[0]
|
||||
} else {
|
||||
ctx.Data["CanForkRepo"] = false
|
||||
ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true)
|
||||
return nil
|
||||
}
|
||||
|
||||
branches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ListOptions: db.ListOptions{
|
||||
ListAll: true,
|
||||
},
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
// Add it as the first option
|
||||
ExcludeBranchNames: []string{ctx.Repo.Repository.DefaultBranch},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindBranchNames", err)
|
||||
return nil
|
||||
}
|
||||
ctx.Data["Branches"] = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
|
||||
|
||||
return forkRepo
|
||||
}
|
||||
|
||||
// Fork render repository fork page
|
||||
func Fork(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
|
||||
if ctx.Doer.CanForkRepo() {
|
||||
ctx.Data["CanForkRepo"] = true
|
||||
} else {
|
||||
maxCreationLimit := ctx.Doer.MaxCreationLimit()
|
||||
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
|
||||
ctx.Flash.Error(msg, true)
|
||||
}
|
||||
|
||||
getForkRepository(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplFork)
|
||||
}
|
||||
|
||||
// ForkPost response for forking a repository
|
||||
func ForkPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.CreateRepoForm)
|
||||
ctx.Data["Title"] = ctx.Tr("new_fork")
|
||||
ctx.Data["CanForkRepo"] = true
|
||||
|
||||
ctxUser := checkContextUser(ctx, form.UID)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
forkRepo := getForkRepository(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["ContextUser"] = ctxUser
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplFork)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
traverseParentRepo := forkRepo
|
||||
for {
|
||||
if ctxUser.ID == traverseParentRepo.OwnerID {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
|
||||
return
|
||||
}
|
||||
repo := repo_model.GetForkedRepo(ctx, ctxUser.ID, traverseParentRepo.ID)
|
||||
if repo != nil {
|
||||
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
|
||||
return
|
||||
}
|
||||
if !traverseParentRepo.IsFork {
|
||||
break
|
||||
}
|
||||
traverseParentRepo, err = repo_model.GetRepositoryByID(ctx, traverseParentRepo.ForkID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is allowed to create repo's on the organization.
|
||||
if ctxUser.IsOrganization() {
|
||||
isAllowedToFork, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("CanCreateOrgRepo", err)
|
||||
return
|
||||
} else if !isAllowedToFork {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
repo, err := repo_service.ForkRepository(ctx, ctx.Doer, ctxUser, repo_service.ForkRepoOptions{
|
||||
BaseRepo: forkRepo,
|
||||
Name: form.RepoName,
|
||||
Description: form.Description,
|
||||
SingleBranch: form.ForkSingleBranch,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Data["Err_RepoName"] = true
|
||||
switch {
|
||||
case repo_model.IsErrReachLimitOfRepo(err):
|
||||
maxCreationLimit := ctxUser.MaxCreationLimit()
|
||||
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
|
||||
ctx.RenderWithErr(msg, tplFork, &form)
|
||||
case repo_model.IsErrRepoAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
|
||||
case repo_model.IsErrRepoFilesAlreadyExist(err):
|
||||
switch {
|
||||
case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplFork, form)
|
||||
case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplFork, form)
|
||||
case setting.Repository.AllowDeleteOfUnadoptedRepositories:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplFork, form)
|
||||
default:
|
||||
ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplFork, form)
|
||||
}
|
||||
case db.IsErrNameReserved(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplFork, &form)
|
||||
case db.IsErrNamePatternNotAllowed(err):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplFork, &form)
|
||||
case errors.Is(err, user_model.ErrBlockedUser):
|
||||
ctx.RenderWithErr(ctx.Tr("repo.fork.blocked_user"), tplFork, form)
|
||||
default:
|
||||
ctx.ServerError("ForkPost", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name)
|
||||
ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
|
||||
}
|
||||
|
||||
func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
|
||||
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||
if err != nil {
|
||||
|
|
|
@ -179,11 +179,9 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori
|
|||
return
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
if err := c.LoadAttachments(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttachments", err)
|
||||
return
|
||||
}
|
||||
if err := comments.LoadAttachments(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttachments", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||
|
|
|
@ -547,9 +547,13 @@ func InitiateDownload(ctx *context.Context) {
|
|||
|
||||
// SearchRepo repositories via options
|
||||
func SearchRepo(ctx *context.Context) {
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
opts := &repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
Page: ctx.FormInt("page"),
|
||||
Page: page,
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
},
|
||||
Actor: ctx.Doer,
|
||||
|
|
|
@ -714,12 +714,16 @@ func UsernameSubRoute(ctx *context.Context) {
|
|||
reloadParam := func(suffix string) (success bool) {
|
||||
ctx.SetParams("username", strings.TrimSuffix(username, suffix))
|
||||
context.UserAssignmentWeb()(ctx)
|
||||
if ctx.Written() {
|
||||
return false
|
||||
}
|
||||
|
||||
// check view permissions
|
||||
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
||||
ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name))
|
||||
return false
|
||||
}
|
||||
return !ctx.Written()
|
||||
return true
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(username, ".png"):
|
||||
|
@ -740,7 +744,6 @@ func UsernameSubRoute(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
if reloadParam(".rss") {
|
||||
context.UserAssignmentWeb()(ctx)
|
||||
feed.ShowUserFeedRSS(ctx)
|
||||
}
|
||||
case strings.HasSuffix(username, ".atom"):
|
||||
|
@ -789,15 +792,15 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
|
|||
case issues_model.FilterModeYourRepositories:
|
||||
openClosedOpts.AllPublic = false
|
||||
case issues_model.FilterModeAssign:
|
||||
openClosedOpts.AssigneeID = &doerID
|
||||
openClosedOpts.AssigneeID = optional.Some(doerID)
|
||||
case issues_model.FilterModeCreate:
|
||||
openClosedOpts.PosterID = &doerID
|
||||
openClosedOpts.PosterID = optional.Some(doerID)
|
||||
case issues_model.FilterModeMention:
|
||||
openClosedOpts.MentionID = &doerID
|
||||
openClosedOpts.MentionID = optional.Some(doerID)
|
||||
case issues_model.FilterModeReviewRequested:
|
||||
openClosedOpts.ReviewRequestedID = &doerID
|
||||
openClosedOpts.ReviewRequestedID = optional.Some(doerID)
|
||||
case issues_model.FilterModeReviewed:
|
||||
openClosedOpts.ReviewedID = &doerID
|
||||
openClosedOpts.ReviewedID = optional.Some(doerID)
|
||||
}
|
||||
openClosedOpts.IsClosed = optional.Some(false)
|
||||
ret.OpenCount, err = issue_indexer.CountIssues(ctx, openClosedOpts)
|
||||
|
@ -815,23 +818,23 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = &doerID }))
|
||||
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = optional.Some(doerID) }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = &doerID }))
|
||||
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = optional.Some(doerID) }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.MentionID = &doerID }))
|
||||
ret.MentionCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.MentionID = optional.Some(doerID) }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewRequestedID = &doerID }))
|
||||
ret.ReviewRequestedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewRequestedID = optional.Some(doerID) }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.ReviewedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewedID = &doerID }))
|
||||
ret.ReviewedCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.ReviewedID = optional.Some(doerID) }))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1374,7 +1374,7 @@ func registerRoutes(m *web.Route) {
|
|||
})
|
||||
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
|
||||
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
|
||||
m.Post("/artifacts", actions.ArtifactsView)
|
||||
m.Get("/artifacts", actions.ArtifactsView)
|
||||
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
|
||||
m.Delete("/artifacts/{artifact_name}", actions.ArtifactsDeleteView)
|
||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||
|
|
|
@ -66,7 +66,7 @@ func ToPullReviewList(ctx context.Context, rl []*issues_model.Review, doer *user
|
|||
result := make([]*api.PullReview, 0, len(rl))
|
||||
for i := range rl {
|
||||
// show pending reviews only for the user who created them
|
||||
if rl[i].Type == issues_model.ReviewTypePending && !(doer.IsAdmin || doer.ID == rl[i].ReviewerID) {
|
||||
if rl[i].Type == issues_model.ReviewTypePending && (doer == nil || (!doer.IsAdmin && doer.ID != rl[i].ReviewerID)) {
|
||||
continue
|
||||
}
|
||||
r, err := ToPullReview(ctx, rl[i], doer)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ToPullReview(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 6})
|
||||
assert.EqualValues(t, reviewer.ID, review.ReviewerID)
|
||||
assert.EqualValues(t, issues_model.ReviewTypePending, review.Type)
|
||||
|
||||
reviewList := []*issues_model.Review{review}
|
||||
|
||||
t.Run("Anonymous User", func(t *testing.T) {
|
||||
prList, err := ToPullReviewList(db.DefaultContext, reviewList, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, prList)
|
||||
})
|
||||
|
||||
t.Run("Reviewer Himself", func(t *testing.T) {
|
||||
prList, err := ToPullReviewList(db.DefaultContext, reviewList, reviewer)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, prList, 1)
|
||||
})
|
||||
|
||||
t.Run("Other User", func(t *testing.T) {
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
prList, err := ToPullReviewList(db.DefaultContext, reviewList, user4)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, prList, 0)
|
||||
})
|
||||
|
||||
t.Run("Admin User", func(t *testing.T) {
|
||||
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
prList, err := ToPullReviewList(db.DefaultContext, reviewList, adminUser)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, prList, 1)
|
||||
})
|
||||
}
|
|
@ -222,7 +222,8 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
|||
body, err := markdown.RenderString(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
Base: ctx.Issue.Repo.HTMLURL(),
|
||||
AbsolutePrefix: true,
|
||||
Base: ctx.Issue.Repo.HTMLURL(),
|
||||
},
|
||||
Metas: ctx.Issue.Repo.ComposeMetas(ctx),
|
||||
}, ctx.Content)
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/quotedprintable"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -19,6 +21,7 @@ import (
|
|||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -67,6 +70,12 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re
|
|||
func TestComposeIssueCommentMessage(t *testing.T) {
|
||||
doer, _, issue, comment := prepareMailerTest(t)
|
||||
|
||||
markup.Init(&markup.ProcessorHelper{
|
||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||
return username == doer.Name
|
||||
},
|
||||
})
|
||||
|
||||
setting.IncomingEmail.Enabled = true
|
||||
defer func() { setting.IncomingEmail.Enabled = false }()
|
||||
|
||||
|
@ -77,7 +86,8 @@ func TestComposeIssueCommentMessage(t *testing.T) {
|
|||
msgs, err := composeIssueCommentMessages(&mailCommentContext{
|
||||
Context: context.TODO(), // TODO: use a correct context
|
||||
Issue: issue, Doer: doer, ActionType: activities_model.ActionCommentIssue,
|
||||
Content: "test body", Comment: comment,
|
||||
Content: fmt.Sprintf("test @%s %s#%d body", doer.Name, issue.Repo.FullName(), issue.Index),
|
||||
Comment: comment,
|
||||
}, "en-US", recipients, false, "issue comment")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, msgs, 2)
|
||||
|
@ -96,6 +106,20 @@ func TestComposeIssueCommentMessage(t *testing.T) {
|
|||
assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetHeader("Message-ID")[0], "Message-ID header doesn't match")
|
||||
assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetHeader("List-Post")[0])
|
||||
assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 2) // url + mailto
|
||||
|
||||
var buf bytes.Buffer
|
||||
gomailMsg.WriteTo(&buf)
|
||||
|
||||
b, err := io.ReadAll(quotedprintable.NewReader(&buf))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// text/plain
|
||||
assert.Contains(t, string(b), fmt.Sprintf(`( %s )`, doer.HTMLURL()))
|
||||
assert.Contains(t, string(b), fmt.Sprintf(`( %s )`, issue.HTMLURL()))
|
||||
|
||||
// text/html
|
||||
assert.Contains(t, string(b), fmt.Sprintf(`href="%s"`, doer.HTMLURL()))
|
||||
assert.Contains(t, string(b), fmt.Sprintf(`href="%s"`, issue.HTMLURL()))
|
||||
}
|
||||
|
||||
func TestComposeIssueMessage(t *testing.T) {
|
||||
|
|
|
@ -110,6 +110,16 @@
|
|||
<div><gitea-origin-url data-url="/test/url"></gitea-origin-url></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>GiteaAbsoluteDate</h1>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="short"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="numeric" day="numeric" month="long"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div>
|
||||
<div><gitea-absolute-date date="2024-03-11T19:00:00-05:00" year="" day="numeric" month="numeric" weekday="long"></gitea-absolute-date></div>
|
||||
<div class="tw-text-text-light-2">relative-time: <relative-time format="datetime" datetime="2024-03-11" year="" day="numeric" month="numeric"></relative-time></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>LocaleNumber</h1>
|
||||
<div>{{ctx.Locale.PrettyNumber 1}}</div>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
{{if or .Labels .Assignees}}
|
||||
<div class="extra content labels-list gt-p-0 gt-pt-2">
|
||||
{{range .Labels}}
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx .}}</a>
|
||||
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
|
||||
{{end}}
|
||||
<div class="right floated">
|
||||
{{range .Assignees}}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<div class="item issue-action gt-df gt-sb" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
|
||||
{{if SliceUtils.Contains $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}
|
||||
{{if SliceUtils.Contains $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context ctx.Locale .}}
|
||||
{{template "repo/issue/labels/label_archived" .}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
{{svg "octicon-check"}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{RenderLabel $.Context .}}
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
<p class="gt-ml-auto">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
{{end}}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
id="label_{{.label.ID}}"
|
||||
href="{{.root.RepoLink}}/{{if or .root.IsPull .root.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.label.ID}}"{{/* FIXME: use .root.Issue.Link or create .root.Link */}}
|
||||
>
|
||||
{{- RenderLabel $.Context .label -}}
|
||||
{{- RenderLabel $.Context ctx.Locale .label -}}
|
||||
</a>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
{{range .Labels}}
|
||||
<li class="item">
|
||||
<div class="label-title">
|
||||
{{RenderLabel $.Context .}}
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
</div>
|
||||
<div class="label-issues">
|
||||
|
@ -72,7 +72,7 @@
|
|||
{{range .OrgLabels}}
|
||||
<li class="item org-label">
|
||||
<div class="label-title">
|
||||
{{RenderLabel $.Context .}}
|
||||
{{RenderLabel $.Context ctx.Locale .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
</div>
|
||||
<div class="label-issues">
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context .}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context ctx.Locale .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{$previousExclusiveScope = $exclusiveScope}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context .}}
|
||||
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" {{if .IsArchived}}data-is-archived{{end}} data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}tw-invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span> {{RenderLabel $.Context ctx.Locale .}}
|
||||
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}
|
||||
<p class="archived-label-hint">{{template "repo/issue/labels/label_archived" .}}</p>
|
||||
</a>
|
||||
|
|
|
@ -173,11 +173,11 @@
|
|||
<span class="text grey muted-links">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{if and .AddedLabels (not .RemovedLabels)}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context .AddedLabels $.RepoLink) $createdStr}}
|
||||
{{ctx.Locale.TrN (len .AddedLabels) "repo.issues.add_label" "repo.issues.add_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink) $createdStr}}
|
||||
{{else if and (not .AddedLabels) .RemovedLabels}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context .RemovedLabels $.RepoLink) $createdStr}}
|
||||
{{ctx.Locale.TrN (len .RemovedLabels) "repo.issues.remove_label" "repo.issues.remove_labels" (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink) $createdStr}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context .AddedLabels $.RepoLink) (RenderLabels $.Context .RemovedLabels $.RepoLink) $createdStr}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_remove_labels" (RenderLabels $.Context ctx.Locale .AddedLabels $.RepoLink) (RenderLabels $.Context ctx.Locale .RemovedLabels $.RepoLink) $createdStr}}
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{{end}}
|
||||
<span class="labels-list gt-ml-2">
|
||||
{{range .Labels}}
|
||||
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{RenderLabel $.Context .}}</a>
|
||||
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">{{RenderLabel $.Context ctx.Locale .}}</a>
|
||||
{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<input readonly="" value="{{.TokenToSign}}">
|
||||
<div class="help">
|
||||
<p>{{ctx.Locale.Tr "settings.gpg_token_help"}}</p>
|
||||
<p><code>{{ctx.Locale.Tr "settings.gpg_token_code" .TokenToSign .PaddedKeyID}}</code></p>
|
||||
<p><code>{{printf `echo "%s" | gpg -a --default-key %s --detach-sig` .TokenToSign .PaddedKeyID}}</code></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
@ -90,7 +90,7 @@
|
|||
<input readonly="" value="{{$.TokenToSign}}">
|
||||
<div class="help">
|
||||
<p>{{ctx.Locale.Tr "settings.gpg_token_help"}}</p>
|
||||
<p><code>{{ctx.Locale.Tr "settings.gpg_token_code" $.TokenToSign .PaddedKeyID}}</code></p>
|
||||
<p><code>{{printf `echo "%s" | gpg -a --default-key %s --detach-sig` $.TokenToSign .PaddedKeyID}}</code></p>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
|
|
@ -243,6 +243,8 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
|
|||
}
|
||||
|
||||
func TestGetUserRss(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user34 := "the_34-user.with.all.allowedChars"
|
||||
req := NewRequestf(t, "GET", "/%s.rss", user34)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
@ -253,6 +255,13 @@ func TestGetUserRss(t *testing.T) {
|
|||
description, _ := rssDoc.ChildrenFiltered("description").Html()
|
||||
assert.EqualValues(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description)
|
||||
}
|
||||
|
||||
req = NewRequestf(t, "GET", "/non-existent-user.rss")
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
req = NewRequestf(t, "GET", "/non-existent-user.rss")
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestListStopWatches(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
exclude: [
|
||||
'@mcaptcha/vanilla-glue', // breaking changes in rc versions need to be handled
|
||||
'eslint-plugin-array-func', // need to migrate to eslint flat config first
|
||||
],
|
||||
};
|
|
@ -2417,6 +2417,10 @@
|
|||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.archived-label {
|
||||
filter: grayscale(0.8);
|
||||
}
|
||||
|
||||
.ui.label.scope-left {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
|
|
@ -5,7 +5,7 @@ import {createApp} from 'vue';
|
|||
import {toggleElem} from '../utils/dom.js';
|
||||
import {formatDatetime} from '../utils/time.js';
|
||||
import {renderAnsi} from '../render/ansi.js';
|
||||
import {POST, DELETE} from '../modules/fetch.js';
|
||||
import {GET, POST, DELETE} from '../modules/fetch.js';
|
||||
|
||||
const sfc = {
|
||||
name: 'RepoActionView',
|
||||
|
@ -196,7 +196,7 @@ const sfc = {
|
|||
},
|
||||
|
||||
async fetchArtifacts() {
|
||||
const resp = await POST(`${this.actionsURL}/runs/${this.runIndex}/artifacts`);
|
||||
const resp = await GET(`${this.actionsURL}/runs/${this.runIndex}/artifacts`);
|
||||
return await resp.json();
|
||||
},
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.js';
|
|||
import {hideElem, showElem} from '../utils/dom.js';
|
||||
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
|
||||
import {attachRefIssueContextPopup} from './contextpopup.js';
|
||||
import {POST} from '../modules/fetch.js';
|
||||
|
||||
const {csrfToken} = window.config;
|
||||
|
||||
|
@ -65,7 +66,7 @@ export function initRepoCommentForm() {
|
|||
const $selectBranch = $('.ui.select-branch');
|
||||
const $branchMenu = $selectBranch.find('.reference-list-menu');
|
||||
const $isNewIssue = $branchMenu.hasClass('new-issue');
|
||||
$branchMenu.find('.item:not(.no-select)').on('click', function () {
|
||||
$branchMenu.find('.item:not(.no-select)').on('click', async function () {
|
||||
const selectedValue = $(this).data('id');
|
||||
const editMode = $('#editing_mode').val();
|
||||
$($(this).data('id-selector')).val(selectedValue);
|
||||
|
@ -76,7 +77,14 @@ export function initRepoCommentForm() {
|
|||
|
||||
if (editMode === 'true') {
|
||||
const form = $('#update_issueref_form');
|
||||
$.post(form.attr('action'), {_csrf: csrfToken, ref: selectedValue}, () => window.location.reload());
|
||||
const params = new URLSearchParams();
|
||||
params.append('ref', selectedValue);
|
||||
try {
|
||||
await POST(form.attr('action'), {data: params});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
} else if (editMode === '') {
|
||||
$selectBranch.find('.ui .branch-name').text(selectedValue);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
This document is used as aria/accessibility(a11y) reference for future developers.
|
||||
|
||||
There are a lot of a11y problems in the Fomantic UI library. This `aria.js` is used
|
||||
as a workaround to make the UI more accessible.
|
||||
There are a lot of a11y problems in the Fomantic UI library. Files in
|
||||
`web_src/js/modules/fomantic/` are used as a workaround to make the UI more accessible.
|
||||
|
||||
The `aria.js` is designed to avoid touching the official Fomantic UI library,
|
||||
The aria-related code is designed to avoid touching the official Fomantic UI library,
|
||||
and to be as independent as possible, so it can be easily modified/removed in the future.
|
||||
|
||||
To test the aria/accessibility with screen readers, developers can use the following steps:
|
||||
|
@ -14,7 +14,7 @@ To test the aria/accessibility with screen readers, developers can use the follo
|
|||
* Press `Command + F5` to turn on VoiceOver.
|
||||
* Try to operate the UI with keyboard-only.
|
||||
* Use Tab/Shift+Tab to switch focus between elements.
|
||||
* Arrow keys to navigate between menu/combobox items (only aria-active, not really focused).
|
||||
* Arrow keys (Option+Up/Down) to navigate between menu/combobox items (only aria-active, not really focused).
|
||||
* Press Enter to trigger the aria-active element.
|
||||
* On Android, you can use TalkBack.
|
||||
* Go to Settings -> Accessibility -> TalkBack, turn it on.
|
||||
|
@ -75,7 +75,7 @@ Fomantic Dropdown is designed to be used for many purposes:
|
|||
Fomantic Dropdown requires that the focus must be on its primary element.
|
||||
If the focus changes, it hides or panics.
|
||||
|
||||
At the moment, `aria.js` only tries to partially resolve the a11y problems for dropdowns with items.
|
||||
At the moment, the aria-related code only tries to partially resolve the a11y problems for dropdowns with items.
|
||||
|
||||
There are different solutions:
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ function updateMenuItem(dropdown, item) {
|
|||
if (!item.id) item.id = generateAriaId();
|
||||
item.setAttribute('role', dropdown[ariaPatchKey].listItemRole);
|
||||
item.setAttribute('tabindex', '-1');
|
||||
for (const a of item.querySelectorAll('a')) a.setAttribute('tabindex', '-1');
|
||||
for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
// make the label item and its "delete icon" has correct aria attributes
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
window.customElements.define('gitea-absolute-date', class extends HTMLElement {
|
||||
static observedAttributes = ['date', 'year', 'month', 'weekday', 'day'];
|
||||
|
||||
update = () => {
|
||||
const year = this.getAttribute('year') ?? '';
|
||||
const month = this.getAttribute('month') ?? '';
|
||||
const weekday = this.getAttribute('weekday') ?? '';
|
||||
const day = this.getAttribute('day') ?? '';
|
||||
const lang = this.closest('[lang]')?.getAttribute('lang') ||
|
||||
this.ownerDocument.documentElement.getAttribute('lang') ||
|
||||
'';
|
||||
|
||||
// only extract the `yyyy-mm-dd` part. When converting to Date, it will become midnight UTC and when rendered
|
||||
// as localized date, will have a offset towards UTC, which we remove to shift the timestamp to midnight in the
|
||||
// localized date. We should eventually use `Temporal.PlainDate` which will make the correction unnecessary.
|
||||
// - https://stackoverflow.com/a/14569783/808699
|
||||
// - https://tc39.es/proposal-temporal/docs/plaindate.html
|
||||
const date = new Date(this.getAttribute('date').substring(0, 10));
|
||||
const correctedDate = new Date(date.getTime() - date.getTimezoneOffset() * -60000);
|
||||
|
||||
if (!this.shadowRoot) this.attachShadow({mode: 'open'});
|
||||
this.shadowRoot.textContent = correctedDate.toLocaleString(lang ?? [], {
|
||||
...(year && {year}),
|
||||
...(month && {month}),
|
||||
...(weekday && {weekday}),
|
||||
...(day && {day}),
|
||||
});
|
||||
};
|
||||
|
||||
attributeChangedCallback(_name, oldValue, newValue) {
|
||||
if (!this.initialized || oldValue === newValue) return;
|
||||
this.update();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.initialized = false;
|
||||
this.update();
|
||||
this.initialized = true;
|
||||
}
|
||||
});
|
|
@ -3,3 +3,4 @@ import './polyfill.js';
|
|||
|
||||
import '@github/relative-time-element';
|
||||
import './GiteaOriginUrl.js';
|
||||
import './GiteaAbsoluteDate.js';
|
||||
|
|
Loading…
Reference in New Issue