Merge pull request '[gitea] week 12 cherry-pick' (#2679) from algernon/forgejo:wcp/week-12 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2679 Reviewed-by: Otto <otto@codeberg.org> Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
commit
2f78daa3af
174 changed files with 3157 additions and 2907 deletions
|
@ -84,7 +84,6 @@ package "code.gitea.io/gitea/models/repo"
|
|||
func (*releaseSorter).Swap
|
||||
func SortReleases
|
||||
func FindReposMapByIDs
|
||||
func RepositoryListOfMap
|
||||
func (SearchOrderBy).String
|
||||
func IsErrTopicNotExist
|
||||
func (ErrTopicNotExist).Error
|
||||
|
@ -178,6 +177,7 @@ package "code.gitea.io/gitea/modules/git"
|
|||
func (ErrExecTimeout).Error
|
||||
func (ErrUnsupportedVersion).Error
|
||||
func SetUpdateHook
|
||||
func GetObjectFormatOfRepo
|
||||
func openRepositoryWithDefaultContext
|
||||
func IsTagExist
|
||||
func ToEntryMode
|
||||
|
|
|
@ -283,7 +283,7 @@ rules:
|
|||
i/unambiguous: [0]
|
||||
init-declarations: [0]
|
||||
jquery/no-ajax-events: [2]
|
||||
jquery/no-ajax: [0]
|
||||
jquery/no-ajax: [2]
|
||||
jquery/no-animate: [2]
|
||||
jquery/no-attr: [0]
|
||||
jquery/no-bind: [2]
|
||||
|
@ -315,7 +315,7 @@ rules:
|
|||
jquery/no-parent: [0]
|
||||
jquery/no-parents: [0]
|
||||
jquery/no-parse-html: [2]
|
||||
jquery/no-prop: [0]
|
||||
jquery/no-prop: [2]
|
||||
jquery/no-proxy: [2]
|
||||
jquery/no-ready: [2]
|
||||
jquery/no-serialize: [2]
|
||||
|
@ -396,11 +396,11 @@ rules:
|
|||
no-irregular-whitespace: [2]
|
||||
no-iterator: [2]
|
||||
no-jquery/no-ajax-events: [2]
|
||||
no-jquery/no-ajax: [0]
|
||||
no-jquery/no-ajax: [2]
|
||||
no-jquery/no-and-self: [2]
|
||||
no-jquery/no-animate-toggle: [2]
|
||||
no-jquery/no-animate: [2]
|
||||
no-jquery/no-append-html: [0]
|
||||
no-jquery/no-append-html: [2]
|
||||
no-jquery/no-attr: [0]
|
||||
no-jquery/no-bind: [2]
|
||||
no-jquery/no-box-model: [2]
|
||||
|
@ -466,7 +466,7 @@ rules:
|
|||
no-jquery/no-parse-html: [2]
|
||||
no-jquery/no-parse-json: [2]
|
||||
no-jquery/no-parse-xml: [2]
|
||||
no-jquery/no-prop: [0]
|
||||
no-jquery/no-prop: [2]
|
||||
no-jquery/no-proxy: [2]
|
||||
no-jquery/no-ready-shorthand: [2]
|
||||
no-jquery/no-ready: [2]
|
||||
|
@ -487,7 +487,7 @@ rules:
|
|||
no-jquery/no-visibility: [2]
|
||||
no-jquery/no-when: [2]
|
||||
no-jquery/no-wrap: [2]
|
||||
no-jquery/variable-pattern: [0]
|
||||
no-jquery/variable-pattern: [2]
|
||||
no-label-var: [2]
|
||||
no-labels: [0] # handled by no-restricted-syntax
|
||||
no-lone-blocks: [2]
|
||||
|
|
4
Makefile
4
Makefile
|
@ -881,10 +881,6 @@ release-sources: | $(DIST_DIRS)
|
|||
release-docs: | $(DIST_DIRS) docs
|
||||
tar -czf $(DIST)/release/gitea-docs-$(VERSION).tar.gz -C ./docs .
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
cd docs; bash scripts/trans-copy.sh;
|
||||
|
||||
.PHONY: deps
|
||||
deps: deps-frontend deps-backend deps-tools deps-py
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ clients don't even support HTML, so they show the text version included in the g
|
|||
|
||||
If the template fails to render, it will be noticed only at the moment the mail is sent.
|
||||
A default subject is used if the subject template fails, and whatever was rendered successfully
|
||||
from the the _mail body_ is used, disregarding the rest.
|
||||
from the _mail body_ is used, disregarding the rest.
|
||||
|
||||
Please check [Gitea's logs](administration/logging-config.md) for error messages in case of trouble.
|
||||
|
||||
|
|
|
@ -333,14 +333,9 @@ Documentation for the website is found in `docs/`. If you change this you
|
|||
can test your changes to ensure that they pass continuous integration using:
|
||||
|
||||
```bash
|
||||
# from the docs directory within Gitea
|
||||
make trans-copy clean build
|
||||
make lint-md
|
||||
```
|
||||
|
||||
You will require a copy of [Hugo](https://gohugo.io/) to run this task. Please
|
||||
note: this may generate a number of untracked Git objects, which will need to
|
||||
be cleaned up.
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
A `launch.json` and `tasks.json` are provided within `contrib/ide/vscode` for
|
||||
|
|
|
@ -307,13 +307,9 @@ TAGS="bindata sqlite sqlite_unlock_notify" make build test-sqlite
|
|||
该网站的文档位于 `docs/` 中。如果你改变了文档内容,你可以使用以下测试方法进行持续集成:
|
||||
|
||||
```bash
|
||||
# 来自 Gitea 中的 docs 目录
|
||||
make trans-copy clean build
|
||||
make lint-md
|
||||
```
|
||||
|
||||
运行此任务依赖于 [Hugo](https://gohugo.io/)。请注意:这可能会生成一些未跟踪的 Git 对象,
|
||||
需要被清理干净。
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
`contrib/ide/vscode` 中为 Visual Studio Code 提供了 `launch.json` 和 `tasks.json`。查看
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
#
|
||||
# This script is used to copy the en-US content to our available locales as a
|
||||
# fallback to always show all pages when displaying a specific locale that is
|
||||
# missing some documents to be translated.
|
||||
#
|
||||
# Just execute the script without any argument and you will get the missing
|
||||
# files copied into the content folder. We are calling this script within the CI
|
||||
# server simply by `make trans-copy`.
|
||||
#
|
||||
|
||||
declare -a LOCALES=(
|
||||
"fr-fr"
|
||||
"nl-nl"
|
||||
"pt-br"
|
||||
"zh-cn"
|
||||
"zh-tw"
|
||||
)
|
||||
|
||||
ROOT=$(realpath $(dirname $0)/..)
|
||||
|
||||
for SOURCE in $(find ${ROOT}/content -type f -iname *.en-us.md); do
|
||||
for LOCALE in "${LOCALES[@]}"; do
|
||||
DEST="${SOURCE%.en-us.md}.${LOCALE}.md"
|
||||
|
||||
if [[ ! -f ${DEST} ]]; then
|
||||
cp ${SOURCE} ${DEST}
|
||||
sed -i.bak "s/en\-us/${LOCALE}/g" ${DEST}
|
||||
rm ${DEST}.bak
|
||||
fi
|
||||
done
|
||||
done
|
4
go.mod
4
go.mod
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
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/buildkite/terminal-to-html/v3 v3.10.1
|
||||
|
@ -169,7 +169,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
|
@ -103,14 +103,14 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
|
|||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
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=
|
||||
|
@ -235,8 +235,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=
|
||||
|
|
|
@ -150,6 +150,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"`
|
||||
|
@ -292,11 +293,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
|
||||
|
@ -305,7 +301,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 "#"
|
||||
}
|
||||
|
@ -313,34 +310,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 "#"
|
||||
}
|
||||
|
@ -348,26 +330,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.
|
||||
|
@ -395,6 +366,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
|
||||
|
@ -405,27 +380,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
|
||||
|
@ -465,7 +465,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
|
||||
|
|
|
@ -476,6 +476,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) {
|
||||
|
|
|
@ -393,7 +393,7 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
|
|||
|
||||
func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session {
|
||||
// Query for pull requests where you are a reviewer or commenter, excluding
|
||||
// any pull requests already returned by the the review requested filter.
|
||||
// any pull requests already returned by the review requested filter.
|
||||
notPoster := builder.Neq{"issue.poster_id": reviewedID}
|
||||
reviewed := builder.In("issue.id", builder.
|
||||
Select("issue_id").
|
||||
|
|
|
@ -553,6 +553,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 {
|
||||
|
|
|
@ -548,17 +548,17 @@ func validateEmailBasic(email string) error {
|
|||
|
||||
// validateEmailDomain checks whether the email domain is allowed or blocked
|
||||
func validateEmailDomain(email string) error {
|
||||
// if there is no allow list, then check email against block list
|
||||
if len(setting.Service.EmailDomainAllowList) == 0 &&
|
||||
validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
// if there is an allow list, then check email against allow list
|
||||
if len(setting.Service.EmailDomainAllowList) > 0 &&
|
||||
!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
|
||||
if !IsEmailDomainAllowed(email) {
|
||||
return ErrEmailInvalid{email}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsEmailDomainAllowed(email string) bool {
|
||||
if len(setting.Service.EmailDomainAllowList) == 0 {
|
||||
return !validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email)
|
||||
}
|
||||
|
||||
return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email)
|
||||
}
|
||||
|
|
|
@ -436,7 +436,7 @@ func (u *User) GetDisplayName() string {
|
|||
return u.Name
|
||||
}
|
||||
|
||||
// GetCompleteName returns the the full name and username in the form of
|
||||
// GetCompleteName returns the full name and username in the form of
|
||||
// "Full Name (username)" if full name is not empty, otherwise it returns
|
||||
// "username".
|
||||
func (u *User) GetCompleteName() string {
|
||||
|
|
|
@ -120,11 +120,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()
|
||||
|
|
|
@ -311,7 +311,7 @@ func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error)
|
|||
return c.repo.GetFilesChangedBetween(pastCommit, c.ID.String())
|
||||
}
|
||||
|
||||
// FileChangedSinceCommit Returns true if the file given has changed since the the past commit
|
||||
// FileChangedSinceCommit Returns true if the file given has changed since the past commit
|
||||
// YOU MUST ENSURE THAT pastCommit is a valid commit ID.
|
||||
func (c *Commit) FileChangedSinceCommit(filename, pastCommit string) (bool, error) {
|
||||
return c.repo.FileChangedBetweenCommits(filename, pastCommit, c.ID.String())
|
||||
|
|
|
@ -152,10 +152,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
|
||||
}
|
||||
|
|
|
@ -233,7 +233,10 @@ func (g *Manager) setStateTransition(old, new state) bool {
|
|||
// At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init,
|
||||
// so this function MUST be called if a server is not used.
|
||||
func (g *Manager) InformCleanup() {
|
||||
g.createServerWaitGroup.Done()
|
||||
g.createServerCond.L.Lock()
|
||||
defer g.createServerCond.L.Unlock()
|
||||
g.createdServer++
|
||||
g.createServerCond.Signal()
|
||||
}
|
||||
|
||||
// Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating
|
||||
|
|
|
@ -42,8 +42,9 @@ type Manager struct {
|
|||
terminateCtxCancel context.CancelFunc
|
||||
managerCtxCancel context.CancelFunc
|
||||
runningServerWaitGroup sync.WaitGroup
|
||||
createServerWaitGroup sync.WaitGroup
|
||||
terminateWaitGroup sync.WaitGroup
|
||||
createServerCond sync.Cond
|
||||
createdServer int
|
||||
shutdownRequested chan struct{}
|
||||
|
||||
toRunAtShutdown []func()
|
||||
|
@ -52,7 +53,7 @@ type Manager struct {
|
|||
|
||||
func newGracefulManager(ctx context.Context) *Manager {
|
||||
manager := &Manager{ctx: ctx, shutdownRequested: make(chan struct{})}
|
||||
manager.createServerWaitGroup.Add(numberOfServersToCreate)
|
||||
manager.createServerCond.L = &sync.Mutex{}
|
||||
manager.prepare(ctx)
|
||||
manager.start()
|
||||
return manager
|
||||
|
|
|
@ -57,20 +57,27 @@ func (g *Manager) start() {
|
|||
// Handle clean up of unused provided listeners and delayed start-up
|
||||
startupDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(startupDone)
|
||||
// Wait till we're done getting all the listeners and then close the unused ones
|
||||
func() {
|
||||
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
|
||||
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
|
||||
// There is no clear solution besides a complete rewriting of the "manager"
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
g.createServerWaitGroup.Wait()
|
||||
defer func() {
|
||||
close(startupDone)
|
||||
// Close the unused listeners and ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
|
||||
_ = CloseProvidedListeners()
|
||||
}()
|
||||
// Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
|
||||
_ = CloseProvidedListeners()
|
||||
g.notify(readyMsg)
|
||||
// Wait for all servers to be created
|
||||
g.createServerCond.L.Lock()
|
||||
for {
|
||||
if g.createdServer >= numberOfServersToCreate {
|
||||
g.createServerCond.L.Unlock()
|
||||
g.notify(readyMsg)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-g.IsShutdown():
|
||||
g.createServerCond.L.Unlock()
|
||||
return
|
||||
default:
|
||||
}
|
||||
g.createServerCond.Wait()
|
||||
}
|
||||
}()
|
||||
if setting.StartupTimeout > 0 {
|
||||
go func() {
|
||||
|
@ -78,16 +85,7 @@ func (g *Manager) start() {
|
|||
case <-startupDone:
|
||||
return
|
||||
case <-g.IsShutdown():
|
||||
func() {
|
||||
// When WaitGroup counter goes negative it will panic - we don't care about this so we can just ignore it.
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
// Ensure that the createServerWaitGroup stops waiting
|
||||
for {
|
||||
g.createServerWaitGroup.Done()
|
||||
}
|
||||
}()
|
||||
g.createServerCond.Signal()
|
||||
return
|
||||
case <-time.After(setting.StartupTimeout):
|
||||
log.Error("Startup took too long! Shutting down")
|
||||
|
|
|
@ -149,33 +149,35 @@ hammerLoop:
|
|||
func (g *Manager) awaitServer(limit time.Duration) bool {
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
func() {
|
||||
// FIXME: there is a fundamental design problem of the "manager" and the "wait group".
|
||||
// If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned
|
||||
// There is no clear solution besides a complete rewriting of the "manager"
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
g.createServerWaitGroup.Wait()
|
||||
}()
|
||||
g.createServerCond.L.Lock()
|
||||
for {
|
||||
if g.createdServer >= numberOfServersToCreate {
|
||||
g.createServerCond.L.Unlock()
|
||||
close(c)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-g.IsShutdown():
|
||||
g.createServerCond.L.Unlock()
|
||||
return
|
||||
default:
|
||||
}
|
||||
g.createServerCond.Wait()
|
||||
}
|
||||
}()
|
||||
|
||||
var tc <-chan time.Time
|
||||
if limit > 0 {
|
||||
select {
|
||||
case <-c:
|
||||
return true // completed normally
|
||||
case <-time.After(limit):
|
||||
return false // timed out
|
||||
case <-g.IsShutdown():
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-c:
|
||||
return true // completed normally
|
||||
case <-g.IsShutdown():
|
||||
return false
|
||||
}
|
||||
tc = time.After(limit)
|
||||
}
|
||||
select {
|
||||
case <-c:
|
||||
return true // completed normally
|
||||
case <-tc:
|
||||
return false // timed out
|
||||
case <-g.IsShutdown():
|
||||
g.createServerCond.Signal()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,11 +91,9 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
|
|||
return nil, runErr
|
||||
}
|
||||
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
|
||||
var err error
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout)
|
||||
return &changes, err
|
||||
}
|
||||
|
@ -174,10 +172,8 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
|
|||
return nil, err
|
||||
}
|
||||
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
|
||||
changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout)
|
||||
return &changes, err
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -6,6 +6,7 @@ package meilisearch
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -170,41 +171,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 == "" {
|
||||
|
@ -217,7 +218,14 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
|
||||
skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits)
|
||||
|
||||
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{
|
||||
keyword := options.Keyword
|
||||
if !options.IsFuzzyKeyword {
|
||||
// to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s)
|
||||
// https://www.meilisearch.com/docs/reference/api/search#phrase-search
|
||||
keyword = doubleQuoteKeyword(keyword)
|
||||
}
|
||||
|
||||
searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{
|
||||
Filter: query.Statement(),
|
||||
Limit: int64(limit),
|
||||
Offset: int64(skip),
|
||||
|
@ -228,7 +236,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
return nil, err
|
||||
}
|
||||
|
||||
hits, err := nonFuzzyWorkaround(searchRes, options.Keyword, options.IsFuzzyKeyword)
|
||||
hits, err := convertHits(searchRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -247,11 +255,20 @@ func parseSortBy(sortBy internal.SortBy) string {
|
|||
return field + ":asc"
|
||||
}
|
||||
|
||||
// nonFuzzyWorkaround is needed as meilisearch does not have an exact search
|
||||
// and you can only change "typo tolerance" per index. So we have to post-filter the results
|
||||
// https://www.meilisearch.com/docs/learn/configuration/typo_tolerance#configuring-typo-tolerance
|
||||
// TODO: remove once https://github.com/orgs/meilisearch/discussions/377 is addressed
|
||||
func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, isFuzzy bool) ([]internal.Match, error) {
|
||||
func doubleQuoteKeyword(k string) string {
|
||||
kp := strings.Split(k, " ")
|
||||
parts := 0
|
||||
for i := range kp {
|
||||
part := strings.Trim(kp[i], "\"")
|
||||
if part != "" {
|
||||
kp[parts] = fmt.Sprintf(`"%s"`, part)
|
||||
parts++
|
||||
}
|
||||
}
|
||||
return strings.Join(kp[:parts], " ")
|
||||
}
|
||||
|
||||
func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) {
|
||||
hits := make([]internal.Match, 0, len(searchRes.Hits))
|
||||
for _, hit := range searchRes.Hits {
|
||||
hit, ok := hit.(map[string]any)
|
||||
|
@ -259,61 +276,11 @@ func nonFuzzyWorkaround(searchRes *meilisearch.SearchResponse, keyword string, i
|
|||
return nil, ErrMalformedResponse
|
||||
}
|
||||
|
||||
if !isFuzzy {
|
||||
keyword = strings.ToLower(keyword)
|
||||
|
||||
// declare a anon func to check if the title, content or at least one comment contains the keyword
|
||||
found, err := func() (bool, error) {
|
||||
// check if title match first
|
||||
title, ok := hit["title"].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if strings.Contains(strings.ToLower(title), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// check if content has a match
|
||||
content, ok := hit["content"].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if strings.Contains(strings.ToLower(content), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// now check for each comment if one has a match
|
||||
// so we first try to cast and skip if there are no comments
|
||||
comments, ok := hit["comments"].([]any)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
} else if len(comments) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// now we iterate over all and report as soon as we detect one match
|
||||
for i := range comments {
|
||||
comment, ok := comments[i].(string)
|
||||
if !ok {
|
||||
return false, ErrMalformedResponse
|
||||
}
|
||||
if strings.Contains(strings.ToLower(comment), keyword) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// we got no match
|
||||
return false, nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
issueID, ok := hit["id"].(float64)
|
||||
if !ok {
|
||||
return nil, ErrMalformedResponse
|
||||
}
|
||||
|
||||
hits = append(hits, internal.Match{
|
||||
ID: int64(issueID),
|
||||
})
|
||||
|
|
|
@ -54,11 +54,10 @@ func TestMeilisearchIndexer(t *testing.T) {
|
|||
tests.TestIndexer(t, indexer)
|
||||
}
|
||||
|
||||
func TestNonFuzzyWorkaround(t *testing.T) {
|
||||
// get unexpected return
|
||||
_, err := nonFuzzyWorkaround(&meilisearch.SearchResponse{
|
||||
func TestConvertHits(t *testing.T) {
|
||||
_, err := convertHits(&meilisearch.SearchResponse{
|
||||
Hits: []any{"aa", "bb", "cc", "dd"},
|
||||
}, "bowling", false)
|
||||
})
|
||||
assert.ErrorIs(t, err, ErrMalformedResponse)
|
||||
|
||||
validResponse := &meilisearch.SearchResponse{
|
||||
|
@ -83,14 +82,15 @@ func TestNonFuzzyWorkaround(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
// nonFuzzy
|
||||
hits, err := nonFuzzyWorkaround(validResponse, "bowling", false)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}}, hits)
|
||||
|
||||
// fuzzy
|
||||
hits, err = nonFuzzyWorkaround(validResponse, "bowling", true)
|
||||
hits, err := convertHits(validResponse)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits)
|
||||
}
|
||||
|
||||
func TestDoubleQuoteKeyword(t *testing.T) {
|
||||
assert.EqualValues(t, "", doubleQuoteKeyword(""))
|
||||
assert.EqualValues(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g"))
|
||||
assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`))
|
||||
}
|
||||
|
|
|
@ -77,29 +77,62 @@ func writeField(w io.Writer, element, class, field string) error {
|
|||
}
|
||||
|
||||
// Render implements markup.Renderer
|
||||
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
tmpBlock := bufio.NewWriter(output)
|
||||
maxSize := setting.UI.CSV.MaxFileSize
|
||||
|
||||
// FIXME: don't read all to memory
|
||||
rawBytes, err := io.ReadAll(input)
|
||||
if maxSize == 0 {
|
||||
return r.tableRender(ctx, input, tmpBlock)
|
||||
}
|
||||
|
||||
rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < int64(len(rawBytes)) {
|
||||
if _, err := tmpBlock.WriteString("<pre>"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tmpBlock.WriteString("</pre>"); err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpBlock.Flush()
|
||||
if int64(len(rawBytes)) <= maxSize {
|
||||
return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock)
|
||||
}
|
||||
return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock)
|
||||
}
|
||||
|
||||
func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error {
|
||||
_, err := tmpBlock.WriteString("<pre>")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes))
|
||||
scan := bufio.NewScanner(input)
|
||||
scan.Split(bufio.ScanRunes)
|
||||
for scan.Scan() {
|
||||
switch scan.Text() {
|
||||
case `&`:
|
||||
_, err = tmpBlock.WriteString("&")
|
||||
case `'`:
|
||||
_, err = tmpBlock.WriteString("'") // "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
case `<`:
|
||||
_, err = tmpBlock.WriteString("<")
|
||||
case `>`:
|
||||
_, err = tmpBlock.WriteString(">")
|
||||
case `"`:
|
||||
_, err = tmpBlock.WriteString(""") // """ is shorter than """.
|
||||
default:
|
||||
_, err = tmpBlock.Write(scan.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tmpBlock.WriteString("</pre>")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpBlock.Flush()
|
||||
}
|
||||
|
||||
func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock *bufio.Writer) error {
|
||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package markup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -29,4 +31,12 @@ func TestRenderCSV(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, v, buf.String())
|
||||
}
|
||||
|
||||
t.Run("fallbackRender", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
err := render.fallbackRender(strings.NewReader("1,<a>\n2,<b>"), bufio.NewWriter(&buf))
|
||||
assert.NoError(t, err)
|
||||
want := "<pre>1,<a>\n2,<b></pre>"
|
||||
assert.Equal(t, want, buf.String())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -579,7 +581,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)
|
||||
|
|
|
@ -105,7 +105,8 @@ func SpecializedMarkdown() goldmark.Markdown {
|
|||
}
|
||||
|
||||
// include language-x class as part of commonmark spec
|
||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + `">`)
|
||||
// the "display" class is used by "js/markup/math.js" to render the code element as a block
|
||||
_, err = w.WriteString(`<code class="chroma language-` + string(language) + ` display">`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -131,11 +131,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 {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var ForgejoVersion = "1.0.0"
|
||||
|
@ -161,9 +162,11 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
|||
func loadRunModeFrom(rootCfg ConfigProvider) {
|
||||
rootSec := rootCfg.Section("")
|
||||
RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername())
|
||||
|
||||
// The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches.
|
||||
// Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly.
|
||||
unsafeAllowRunAsRoot := ConfigSectionKeyBool(rootSec, "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")
|
||||
unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || util.OptionalBoolParse(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value()
|
||||
RunMode = os.Getenv("GITEA_RUN_MODE")
|
||||
if RunMode == "" {
|
||||
RunMode = rootSec.Key("RUN_MODE").MustString("prod")
|
||||
|
|
|
@ -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,
|
||||
|
@ -229,6 +229,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")
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestMain(m *testing.M) {
|
|||
|
||||
func TestApostrophesInMentions(t *testing.T) {
|
||||
rendered := RenderMarkdownToHtml(context.Background(), "@mention-user's comment")
|
||||
assert.EqualValues(t, "<p><a href=\"http://localhost:3000/mention-user\" rel=\"nofollow\">@mention-user</a>'s comment</p>\n", rendered)
|
||||
assert.EqualValues(t, template.HTML("<p><a href=\"/mention-user\" rel=\"nofollow\">@mention-user</a>'s comment</p>\n"), rendered)
|
||||
}
|
||||
|
||||
func TestRenderCommitBody(t *testing.T) {
|
||||
|
@ -122,21 +122,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))
|
||||
}
|
||||
|
@ -160,14 +160,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>
|
||||
|
@ -184,7 +184,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>
|
||||
`
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
|
||||
// DateTime renders an absolute time HTML element by datetime.
|
||||
func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
||||
// TODO: remove the extraAttrs argument, it's not used in any call to DateTime
|
||||
|
||||
if p, ok := datetime.(*time.Time); ok {
|
||||
datetime = *p
|
||||
}
|
||||
|
@ -51,18 +53,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(`<absolute-date %s date="%s">%s</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, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</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, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</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, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
||||
|
||||
actual = DateTime("short", refDateStr)
|
||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</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, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</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)
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML {
|
|||
}
|
||||
|
||||
// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
|
||||
htm := fmt.Sprintf(`<relative-time class="time-since" prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
|
||||
htm := fmt.Sprintf(`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
|
||||
attrs, then.Format(time.RFC3339), friendlyText)
|
||||
return template.HTML(htm)
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ func timeSinceUnix(then, now time.Time, _ translation.Locale) template.HTML {
|
|||
// TimeSince renders relative time HTML given a time.Time
|
||||
func TimeSince(then time.Time, lang translation.Locale) template.HTML {
|
||||
if setting.UI.PreferredTimestampTense == "absolute" {
|
||||
return DateTime("full", then, `class="time-since"`)
|
||||
return DateTime("full", then)
|
||||
}
|
||||
return timeSinceUnix(then, time.Now(), lang)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
1416
package-lock.json
generated
1416
package-lock.json
generated
File diff suppressed because it is too large
Load diff
29
package.json
29
package.json
|
@ -4,9 +4,9 @@
|
|||
"node": ">= 18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@citation-js/core": "0.7.6",
|
||||
"@citation-js/plugin-bibtex": "0.7.8",
|
||||
"@citation-js/plugin-csl": "0.7.6",
|
||||
"@citation-js/core": "0.7.9",
|
||||
"@citation-js/plugin-bibtex": "0.7.9",
|
||||
"@citation-js/plugin-csl": "0.7.9",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@claviska/jquery-minicolors": "2.3.6",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
|
@ -14,7 +14,6 @@
|
|||
"@github/text-expander-element": "2.6.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.8.0",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"ansi_up": "6.0.2",
|
||||
"asciinema-player": "3.7.0",
|
||||
|
@ -26,26 +25,28 @@
|
|||
"dayjs": "1.11.10",
|
||||
"dropzone": "6.0.0-beta.2",
|
||||
"easymde": "2.18.0",
|
||||
"esbuild-loader": "4.0.3",
|
||||
"esbuild-loader": "4.1.0",
|
||||
"escape-goat": "4.0.0",
|
||||
"fast-glob": "3.3.2",
|
||||
"htmx.org": "1.9.10",
|
||||
"htmx.org": "1.9.11",
|
||||
"idiomorph": "0.3.0",
|
||||
"jquery": "3.7.1",
|
||||
"katex": "0.16.9",
|
||||
"license-checker-webpack-plugin": "0.2.1",
|
||||
"mermaid": "10.8.0",
|
||||
"mermaid": "10.9.0",
|
||||
"mini-css-extract-plugin": "2.8.1",
|
||||
"minimatch": "9.0.3",
|
||||
"monaco-editor": "0.46.0",
|
||||
"monaco-editor": "0.47.0",
|
||||
"monaco-editor-webpack-plugin": "7.1.0",
|
||||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "12.1.0",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.11.8",
|
||||
"swagger-ui-dist": "5.12.0",
|
||||
"tailwindcss": "3.4.1",
|
||||
"temporal-polyfill": "0.2.3",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tippy.js": "6.3.7",
|
||||
|
@ -65,7 +66,7 @@
|
|||
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@stoplight/spectral-cli": "6.11.0",
|
||||
"@stylistic/eslint-plugin-js": "1.6.3",
|
||||
"@stylistic/eslint-plugin-js": "1.7.0",
|
||||
"@stylistic/stylelint-plugin": "2.1.0",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"eslint": "8.57.0",
|
||||
|
@ -75,12 +76,12 @@
|
|||
"eslint-plugin-jquery": "1.5.1",
|
||||
"eslint-plugin-no-jquery": "2.7.0",
|
||||
"eslint-plugin-no-use-extend-native": "0.5.0",
|
||||
"eslint-plugin-regexp": "2.2.0",
|
||||
"eslint-plugin-regexp": "2.3.0",
|
||||
"eslint-plugin-sonarjs": "0.24.0",
|
||||
"eslint-plugin-unicorn": "51.0.1",
|
||||
"eslint-plugin-vitest": "0.3.22",
|
||||
"eslint-plugin-vitest": "0.3.26",
|
||||
"eslint-plugin-vitest-globals": "1.4.0",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.7.2",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"jsdom": "24.0.0",
|
||||
|
@ -90,7 +91,7 @@
|
|||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.4",
|
||||
"svgo": "3.2.0",
|
||||
"updates": "15.1.2",
|
||||
"updates": "15.3.1",
|
||||
"vite-string-plugin": "1.1.5",
|
||||
"vitest": "1.3.1"
|
||||
},
|
||||
|
|
|
@ -147,6 +147,11 @@ func CreateUser(ctx *context.APIContext) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !user_model.IsEmailDomainAllowed(u.Email) {
|
||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
|
||||
}
|
||||
|
||||
log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
|
@ -220,6 +225,10 @@ func EditUser(ctx *context.APIContext) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !user_model.IsEmailDomainAllowed(*form.Email) {
|
||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
|
||||
}
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,7 +368,7 @@ func ListIssues(ctx *context.APIContext) {
|
|||
// required: false
|
||||
// - name: created_by
|
||||
// in: query
|
||||
// description: Only show items which were created by the the given user
|
||||
// description: Only show items which were created by the given user
|
||||
// type: string
|
||||
// - name: assigned_by
|
||||
// in: query
|
||||
|
@ -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)
|
||||
|
|
|
@ -1065,6 +1065,8 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||
return nil, nil, nil, nil, "", ""
|
||||
}
|
||||
headBranch = headInfos[1]
|
||||
// The head repository can also point to the same repo
|
||||
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
||||
|
||||
} else {
|
||||
ctx.NotFound()
|
||||
|
|
|
@ -692,7 +692,7 @@ func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues
|
|||
return nil, nil, true
|
||||
}
|
||||
|
||||
// validate the the review is for the given PR
|
||||
// validate the review is for the given PR
|
||||
if review.IssueID != pr.IssueID {
|
||||
ctx.NotFound("ReviewNotInPR")
|
||||
return nil, nil, true
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -202,6 +202,11 @@ func NewUserPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !user_model.IsEmailDomainAllowed(u.Email) {
|
||||
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
|
||||
}
|
||||
|
||||
log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
|
@ -425,6 +430,9 @@ func EditUserPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
if !user_model.IsEmailDomainAllowed(form.Email) {
|
||||
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", form.Email))
|
||||
}
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
|
|
|
@ -148,12 +148,7 @@ func RestoreBranchPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
|
||||
if err != nil {
|
||||
log.Error("RestoreBranch: CreateBranch: %w", err)
|
||||
ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", deletedBranch.Name))
|
||||
return
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName)
|
||||
|
||||
// Don't return error below this
|
||||
if err := repo_service.PushUpdate(
|
||||
|
|
|
@ -2636,9 +2636,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,
|
||||
|
@ -2667,28 +2667,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2795,9 +2795,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]()
|
||||
|
@ -2835,10 +2835,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
|
||||
|
@ -2859,13 +2859,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)
|
||||
|
|
|
@ -543,9 +543,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,
|
||||
|
|
|
@ -616,6 +616,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
|||
return nil, nil
|
||||
}
|
||||
ctx.Data["BaseLink"] = orCtx.Link
|
||||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||
|
||||
var w *webhook.Webhook
|
||||
if orCtx.RepoID > 0 {
|
||||
|
@ -684,12 +685,7 @@ func TestWebhook(ctx *context.Context) {
|
|||
commit := ctx.Repo.Commit
|
||||
if commit == nil {
|
||||
ghost := user_model.NewGhostUser()
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, ctx.Repo.Repository.RepoPath())
|
||||
if err != nil {
|
||||
ctx.Flash.Error("GetObjectFormatOfRepo: " + err.Error())
|
||||
ctx.Status(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName)
|
||||
commit = &git.Commit{
|
||||
ID: objectFormat.EmptyObjectID(),
|
||||
Author: ghost.NewGitSig(),
|
||||
|
|
|
@ -791,15 +791,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)
|
||||
|
@ -817,23 +817,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
|
||||
}
|
||||
|
|
|
@ -1413,7 +1413,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}", reqRepoActionsWriter, actions.ArtifactsDeleteView)
|
||||
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
"strings"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
|
@ -109,11 +109,7 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
|
|||
// domains in the whitelist or if it doesn't match any of
|
||||
// domains in the blocklist, if any such list is not empty.
|
||||
func (f *RegisterForm) IsEmailDomainAllowed() bool {
|
||||
if len(setting.Service.EmailDomainAllowList) == 0 {
|
||||
return !validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, f.Email)
|
||||
}
|
||||
|
||||
return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, f.Email)
|
||||
return user_model.IsEmailDomainAllowed(f.Email)
|
||||
}
|
||||
|
||||
// MustChangePasswordForm form for updating your password after account creation
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -479,10 +479,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
|
|||
log.Error("SyncMirrors [repo: %-v]: unable to GetRefCommitID [ref_name: %s]: %v", m.Repo, result.refName, err)
|
||||
continue
|
||||
}
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, m.Repo.RepoPath())
|
||||
if err != nil {
|
||||
log.Error("SyncMirrors [repo: %-v]: unable to GetHashTypeOfRepo: %v", m.Repo, err)
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(m.Repo.ObjectFormatName)
|
||||
notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
|
||||
RefFullName: result.refName,
|
||||
OldCommitID: objectFormat.EmptyObjectID().String(),
|
||||
|
|
|
@ -351,7 +351,7 @@ func TestPullRequest(ctx context.Context, doer *user_model.User, repoID, maxPR i
|
|||
}
|
||||
if err == nil {
|
||||
for _, pr := range prs {
|
||||
objectFormat, _ := git.GetObjectFormatOfRepo(ctx, pr.BaseRepo.RepoPath())
|
||||
objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
|
||||
if newCommitID != "" && newCommitID != objectFormat.EmptyObjectID().String() {
|
||||
changed, err := checkIfPRContentChanged(ctx, pr, oldCommitID, newCommitID)
|
||||
if err != nil {
|
||||
|
|
|
@ -326,10 +326,7 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re
|
|||
}
|
||||
|
||||
refName := git.RefNameFromTag(rel.TagName)
|
||||
objectFormat, err := git.GetObjectFormatOfRepo(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
notify_service.PushCommits(
|
||||
ctx, doer, repo,
|
||||
&repository.PushUpdateOptions{
|
||||
|
|
|
@ -41,6 +41,7 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
|
|||
remove_label_str: {{ctx.Locale.Tr "remove_label_str"}},
|
||||
modal_confirm: {{ctx.Locale.Tr "modal.confirm"}},
|
||||
modal_cancel: {{ctx.Locale.Tr "modal.cancel"}},
|
||||
more_items: {{ctx.Locale.Tr "more_items"}},
|
||||
},
|
||||
};
|
||||
{{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
|
||||
|
|
|
@ -105,9 +105,46 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<h1>GiteaOriginUrl</h1>
|
||||
<div><gitea-origin-url data-url="test/url"></gitea-origin-url></div>
|
||||
<div><gitea-origin-url data-url="/test/url"></gitea-origin-url></div>
|
||||
<h1><origin-url></h1>
|
||||
<div><origin-url data-url="test/url"></origin-url></div>
|
||||
<div><origin-url data-url="/test/url"></origin-url></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1><overflow-menu></h1>
|
||||
<overflow-menu class="ui secondary pointing tabular borderless menu">
|
||||
<div class="overflow-menu-items">
|
||||
<a class="active item">item</a>
|
||||
<a class="item">item 1</a>
|
||||
<a class="item">item 2</a>
|
||||
<a class="item">item 3</a>
|
||||
<a class="item">item 4</a>
|
||||
<a class="item">item 5</a>
|
||||
<a class="item">item 6</a>
|
||||
<a class="item">item 7</a>
|
||||
<a class="item">item 8</a>
|
||||
<a class="item">item 9</a>
|
||||
<a class="item">item 10</a>
|
||||
<a class="item">item 11</a>
|
||||
<a class="item">item 12</a>
|
||||
<a class="item">item 13</a>
|
||||
<a class="item">item 14</a>
|
||||
<a class="item">item 15</a>
|
||||
<a class="item">item 16</a>
|
||||
<a class="item">item 17</a>
|
||||
<a class="item">item 18</a>
|
||||
</div>
|
||||
</overflow-menu>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>GiteaAbsoluteDate</h1>
|
||||
<div><absolute-date date="2024-03-11" year="numeric" day="numeric" month="short"></absolute-date></div>
|
||||
<div><absolute-date date="2024-03-11" year="numeric" day="numeric" month="long"></absolute-date></div>
|
||||
<div><absolute-date date="2024-03-11" year="" day="numeric" month="numeric"></absolute-date></div>
|
||||
<div><absolute-date date="2024-03-11" year="" day="numeric" month="numeric" weekday="long"></absolute-date></div>
|
||||
<div><absolute-date date="2024-03-11T19:00:00-05:00" year="" day="numeric" month="numeric" weekday="long"></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>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="ui secondary pointing tabular top attached borderless menu new-menu navbar">
|
||||
<div class="new-menu-inner">
|
||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar">
|
||||
<div class="overflow-menu-items tw-justify-center">
|
||||
<a class="{{if .PageIsExploreRepositories}}active {{end}}item" href="{{AppSubUrl}}/explore/repos">
|
||||
{{svg "octicon-repo"}} {{ctx.Locale.Tr "explore.repos"}}
|
||||
</a>
|
||||
|
@ -17,4 +17,4 @@
|
|||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</overflow-menu>
|
||||
|
|
|
@ -1,50 +1,49 @@
|
|||
<div class="ui container">
|
||||
<div class="ui secondary stackable pointing menu">
|
||||
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
|
||||
{{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}}
|
||||
{{if .RepoCount}}
|
||||
<div class="ui small label">{{.RepoCount}}</div>
|
||||
<overflow-menu class="ui secondary pointing tabular borderless menu">
|
||||
<div class="overflow-menu-items">
|
||||
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
|
||||
{{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}}
|
||||
{{if .RepoCount}}
|
||||
<div class="ui small label">{{.RepoCount}}</div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{if .CanReadProjects}}
|
||||
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
|
||||
{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
|
||||
{{if .ProjectCount}}
|
||||
<div class="ui small label">{{.ProjectCount}}</div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</a>
|
||||
{{if .CanReadProjects}}
|
||||
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
|
||||
{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
|
||||
{{if .ProjectCount}}
|
||||
<div class="ui small label">{{.ProjectCount}}</div>
|
||||
{{if and .IsPackageEnabled .CanReadPackages}}
|
||||
<a class="{{if .IsPackagesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/packages">
|
||||
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if and .IsPackageEnabled .CanReadPackages}}
|
||||
<a class="{{if .IsPackagesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/packages">
|
||||
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if and .IsRepoIndexerEnabled .CanReadCode}}
|
||||
<a class="{{if .IsCodePage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "org.code"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .NumMembers}}
|
||||
{{if and .IsRepoIndexerEnabled .CanReadCode}}
|
||||
<a class="{{if .IsCodePage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "org.code"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .NumMembers}}
|
||||
<a class="{{if $.PageIsOrgMembers}}active {{end}}item" href="{{$.OrgLink}}/members">
|
||||
{{svg "octicon-person"}} {{ctx.Locale.Tr "org.members"}}
|
||||
<div class="ui small label">{{.NumMembers}}</div>
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .IsOrganizationMember}}
|
||||
{{end}}
|
||||
{{if .IsOrganizationMember}}
|
||||
<a class="{{if $.PageIsOrgTeams}}active {{end}}item" href="{{$.OrgLink}}/teams">
|
||||
{{svg "octicon-people"}} {{ctx.Locale.Tr "org.teams"}}
|
||||
{{if .NumTeams}}
|
||||
<div class="ui small label">{{.NumTeams}}</div>
|
||||
{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
{{if .IsOrganizationOwner}}
|
||||
<div class="right menu">
|
||||
<a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings">
|
||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .IsOrganizationOwner}}
|
||||
<a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings">
|
||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</overflow-menu>
|
||||
</div>
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.alpine.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine"></gitea-origin-url>/$branch/$repository</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code><origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine"></origin-url>/$branch/$repository</code></pre></div>
|
||||
<p>{{ctx.Locale.Tr "packages.alpine.registry.info"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alpine.registry.key"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>curl -JO <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine/key"></gitea-origin-url></code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>curl -JO <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alpine/key"></origin-url></code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alpine.install"}}</label>
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
default = "forgejo"
|
||||
|
||||
[registries.forgejo]
|
||||
index = "sparse+<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cargo/"></gitea-origin-url>" # Sparse index
|
||||
# index = "<gitea-origin-url data-url="{{AppSubUrl}}/{{.PackageDescriptor.Owner.Name}}/_cargo-index.git"></gitea-origin-url>" # Git
|
||||
index = "sparse+<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cargo/"></origin-url>" # Sparse index
|
||||
# index = "<origin-url data-url="{{AppSubUrl}}/{{.PackageDescriptor.Owner.Name}}/_cargo-index.git"></origin-url>" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true</code></pre></div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.chef.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>knife[:supermarket_site] = '<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/chef"></gitea-origin-url>'</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>knife[:supermarket_site] = '<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/chef"></origin-url>'</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.chef.install"}}</label>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="markup"><pre class="code-block"><code>{
|
||||
"repositories": [{
|
||||
"type": "composer",
|
||||
"url": "<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/composer"></gitea-origin-url>"
|
||||
"url": "<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/composer"></origin-url>"
|
||||
}
|
||||
]
|
||||
}</code></pre></div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conan.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>conan remote add gitea <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conan"></gitea-origin-url></code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>conan remote add gitea <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conan"></origin-url></code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conan.install"}}</label>
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.conda.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>channel_alias: <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url>
|
||||
<div class="markup"><pre class="code-block"><code>channel_alias: <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url>
|
||||
channels:
|
||||
  - <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url>
|
||||
  - <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url>
|
||||
default_channels:
|
||||
  - <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></gitea-origin-url></code></pre></div>
|
||||
  - <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/conda"></origin-url></code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.conda.install"}}</label>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-code"}} {{ctx.Locale.Tr "packages.cran.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>options("repos" = c(getOption("repos"), c(forgejo="<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cran"></gitea-origin-url>")))</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>options("repos" = c(getOption("repos"), c(forgejo="<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/cran"></origin-url>")))</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.cran.install"}}</label>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.debian.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>sudo curl <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key"></gitea-origin-url> -o /etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc
|
||||
echo "deb [signed-by=/etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc] <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian"></gitea-origin-url> $distribution $component" | sudo tee -a /etc/apt/sources.list.d/forgejo.list
|
||||
<div class="markup"><pre class="code-block"><code>sudo curl <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key"></origin-url> -o /etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc
|
||||
echo "deb [signed-by=/etc/apt/keyrings/forgejo-{{$.PackageDescriptor.Owner.Name}}.asc] <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/debian"></origin-url> $distribution $component" | sudo tee -a /etc/apt/sources.list.d/forgejo.list
|
||||
sudo apt update</code></pre></div>
|
||||
<p>{{ctx.Locale.Tr "packages.debian.registry.info"}}</p>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.generic.download"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>
|
||||
{{- range .PackageDescriptor.Files -}}
|
||||
curl -OJ <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/generic/{{$.PackageDescriptor.Package.Name}}/{{$.PackageDescriptor.Version.Version}}/{{.File.Name}}"></gitea-origin-url>
|
||||
curl -OJ <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/generic/{{$.PackageDescriptor.Package.Name}}/{{$.PackageDescriptor.Version.Version}}/{{.File.Name}}"></origin-url>
|
||||
{{end -}}
|
||||
</code></pre></div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.go.install"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>GOPROXY=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/go"></gitea-origin-url> go install {{$.PackageDescriptor.Package.Name}}@{{$.PackageDescriptor.Version.Version}}</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>GOPROXY=<origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/go"></origin-url> go install {{$.PackageDescriptor.Package.Name}}@{{$.PackageDescriptor.Version.Version}}</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "packages.registry.documentation" "Go" "https://forgejo.org/docs/latest/user/packages/go/"}}</label>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.helm.registry"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>helm repo add {{AppDomain}} <gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/helm"></gitea-origin-url>
|
||||
<div class="markup"><pre class="code-block"><code>helm repo add {{AppDomain}} <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/helm"></origin-url>
|
||||
helm repo update</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
|
|
@ -7,19 +7,19 @@
|
|||
<div class="markup"><pre class="code-block"><code><repositories>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url></url>
|
||||
<url><origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url></url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url></url>
|
||||
<url><origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url></url>
|
||||
</repository>
|
||||
|
||||
<snapshotRepository>
|
||||
<id>gitea</id>
|
||||
<url><gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url></url>
|
||||
<url><origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url></url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement></code></pre></div>
|
||||
</div>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.maven.download"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>mvn dependency:get -DremoteRepositories=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></gitea-origin-url> -Dartifact={{.PackageDescriptor.Metadata.GroupID}}:{{.PackageDescriptor.Metadata.ArtifactID}}:{{.PackageDescriptor.Version.Version}}</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>mvn dependency:get -DremoteRepositories=<origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/maven"></origin-url> -Dartifact={{.PackageDescriptor.Metadata.GroupID}}:{{.PackageDescriptor.Metadata.ArtifactID}}:{{.PackageDescriptor.Version.Version}}</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "packages.registry.documentation" "Maven" "https://forgejo.org/docs/latest/user/packages/maven/"}}</label>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue