Merge branch 'main' of https://github.com/go-gitea/gitea into workflow-webhook-api

This commit is contained in:
Christopher Homberger
2025-03-17 21:49:12 +01:00
176 changed files with 2939 additions and 1883 deletions
+15 -5
View File
@@ -62,7 +62,7 @@ func (m *MaterialIconProvider) loadData() {
log.Debug("Loaded material icon rules and SVG images")
}
func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg string) template.HTML {
func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg, extraClass string) template.HTML {
data := ctx.GetData()
renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool)
if renderedSVGs == nil {
@@ -75,7 +75,7 @@ func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name
panic("Invalid SVG icon")
}
svgID := "svg-mfi-" + name
svgCommonAttrs := `class="svg fileicon" width="16" height="16" aria-hidden="true"`
svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
posOuterBefore := strings.IndexByte(svg, '>')
if renderedSVGs[svgID] && posOuterBefore != -1 {
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
@@ -92,7 +92,8 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
if entry.IsLink() {
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
return svg.RenderHTML("material-folder-symlink")
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink")
}
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
}
@@ -100,10 +101,19 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
name := m.findIconNameByGit(entry)
if name == "folder" {
// the material icon pack's "folder" icon doesn't look good, so use our built-in one
return svg.RenderHTML("material-folder-generic")
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
return svg.RenderHTML("material-folder-generic", 16, "octicon-file-directory-fill")
}
if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" {
return m.renderFileIconSVG(ctx, name, iconSVG)
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
extraClass := "octicon-file"
switch {
case entry.IsDir():
extraClass = "octicon-file-directory-fill"
case entry.IsSubModule():
extraClass = "octicon-file-submodule"
}
return m.renderFileIconSVG(ctx, name, iconSVG, extraClass)
}
return svg.RenderHTML("octicon-file")
}
+2
View File
@@ -46,6 +46,7 @@ type Command struct {
args []string
globalArgsLength int
brokenArgs []string
cmd *exec.Cmd // for debug purpose only
}
func logArgSanitize(arg string) string {
@@ -314,6 +315,7 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
startTime := time.Now()
cmd := exec.CommandContext(ctx, c.prog, c.args...)
c.cmd = cmd // for debug purpose only
if opts.Env == nil {
cmd.Env = os.Environ()
} else {
+1 -1
View File
@@ -19,7 +19,7 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil)
}
// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory
// parseTreeEntries FIXME this function's design is not right, it should not make the caller read all data into memory
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
for pos := 0; pos < len(data); {
+32 -15
View File
@@ -9,6 +9,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"code.gitea.io/gitea/modules/log"
)
@@ -102,7 +104,7 @@ type CheckAttributeReader struct {
stdinReader io.ReadCloser
stdinWriter *os.File
stdOut attributeWriter
stdOut *nulSeparatedAttributeWriter
cmd *Command
env []string
ctx context.Context
@@ -152,7 +154,6 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
return nil
}
// Run run cmd
func (c *CheckAttributeReader) Run() error {
defer func() {
_ = c.stdinReader.Close()
@@ -176,7 +177,7 @@ func (c *CheckAttributeReader) Run() error {
func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
defer func() {
if err != nil && err != c.ctx.Err() {
log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err)
}
}()
@@ -191,9 +192,31 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
return nil, err
}
reportTimeout := func() error {
stdOutClosed := false
select {
case <-c.stdOut.closed:
stdOutClosed = true
default:
}
debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path))
debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
if c.cmd.cmd != nil {
debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String())
}
_ = c.Close()
return fmt.Errorf("CheckPath timeout: %s", debugMsg)
}
rs = make(map[string]string)
for range c.Attributes {
select {
case <-time.After(5 * time.Second):
// There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath
// So add a timeout here to mitigate the problem, and output more logs for debug purpose
// In real world, if CheckPath runs long than seconds, it blocks the end user's operation,
// and at the moment the CheckPath result is not so important, so we can just ignore it.
return nil, reportTimeout()
case attr, ok := <-c.stdOut.ReadAttribute():
if !ok {
return nil, c.ctx.Err()
@@ -206,18 +229,12 @@ func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err
return rs, nil
}
// Close close pip after use
func (c *CheckAttributeReader) Close() error {
c.cancel()
err := c.stdinWriter.Close()
return err
}
type attributeWriter interface {
io.WriteCloser
ReadAttribute() <-chan attributeTriple
}
type attributeTriple struct {
Filename string
Attribute string
@@ -281,7 +298,7 @@ func (wr *nulSeparatedAttributeWriter) Close() error {
return nil
}
// Create a check attribute reader for the current repository and provided commit ID
// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID
func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
if err != nil {
@@ -303,21 +320,21 @@ func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeRe
}
ctx, cancel := context.WithCancel(repo.Ctx)
if err := checker.Init(ctx); err != nil {
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err)
} else {
go func() {
err := checker.Run()
if err != nil && err != ctx.Err() {
log.Error("Unable to open checker for %s. Error: %v", commitID, err)
if err != nil && !IsErrCanceledOrKilled(err) {
log.Error("Attribute checker for commit %s exits with error: %v", commitID, err)
}
cancel()
}()
}
deferable := func() {
deferrable := func() {
_ = checker.Close()
cancel()
deleteTemporaryFile()
}
return checker, deferable
return checker, deferrable
}
+60
View File
@@ -4,10 +4,16 @@
package git
import (
"context"
mathRand "math/rand/v2"
"path/filepath"
"slices"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
@@ -95,3 +101,57 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
Value: "unspecified",
}, attr)
}
func TestAttributeReader(t *testing.T) {
t.Skip() // for debug purpose only, do not run in CI
ctx := t.Context()
timeout := 1 * time.Second
repoPath := filepath.Join(testReposDir, "language_stats_repo")
commitRef := "HEAD"
oneRound := func(t *testing.T, roundIdx int) {
ctx, cancel := context.WithTimeout(ctx, timeout)
_ = cancel
gitRepo, err := OpenRepository(ctx, repoPath)
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(commitRef)
require.NoError(t, err)
files, err := gitRepo.LsFiles()
require.NoError(t, err)
randomFiles := slices.Clone(files)
randomFiles = append(randomFiles, "any-file-1", "any-file-2")
t.Logf("Round %v with %d files", roundIdx, len(randomFiles))
attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String())
defer deferrable()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
file := randomFiles[mathRand.IntN(len(randomFiles))]
_, err := attrReader.CheckPath(file)
if err != nil {
for i := 0; i < 10; i++ {
_, _ = attrReader.CheckPath(file)
}
break
}
}
wg.Done()
}()
wg.Wait()
}
for i := 0; i < 100; i++ {
oneRound(t, i)
}
}
-6
View File
@@ -5,7 +5,6 @@
package git
import (
"context"
"fmt"
"io"
"strings"
@@ -17,11 +16,6 @@ import (
// TagPrefix tags prefix path on the repository
const TagPrefix = "refs/tags/"
// IsTagExist returns true if given tag exists in the repository.
func IsTagExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(ctx, repoPath, TagPrefix+name)
}
// CreateTag create one tag in the repository
func (repo *Repository) CreateTag(name, revision string) error {
_, _, err := NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+1
View File
@@ -21,6 +21,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
return &TreeEntry{
ID: t.ID,
// Type: ObjectTree,
ptree: t,
gogitTreeEntry: &object.TreeEntry{
Name: "",
Mode: filemode.Dir,
+18
View File
@@ -47,3 +47,21 @@ func GetDefaultBranch(ctx context.Context, repo Repository) (string, error) {
func GetWikiDefaultBranch(ctx context.Context, repo Repository) (string, error) {
return git.GetDefaultBranch(ctx, wikiPath(repo))
}
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repo Repository, name string) bool {
return git.IsReferenceExist(ctx, repoPath(repo), name)
}
func IsWikiReferenceExist(ctx context.Context, repo Repository, name string) bool {
return git.IsReferenceExist(ctx, wikiPath(repo), name)
}
// IsBranchExist returns true if given branch exists in the repository.
func IsBranchExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.BranchPrefix+name)
}
func IsWikiBranchExist(ctx context.Context, repo Repository, name string) bool {
return IsWikiReferenceExist(ctx, repo, git.BranchPrefix+name)
}
+20 -1
View File
@@ -5,6 +5,7 @@ package gitrepo
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
@@ -20,8 +21,12 @@ type Repository interface {
GetOwnerName() string
}
func absPath(owner, name string) string {
return filepath.Join(setting.RepoRootPath, strings.ToLower(owner), strings.ToLower(name)+".git")
}
func repoPath(repo Repository) string {
return filepath.Join(setting.RepoRootPath, strings.ToLower(repo.GetOwnerName()), strings.ToLower(repo.GetName())+".git")
return absPath(repo.GetOwnerName(), repo.GetName())
}
func wikiPath(repo Repository) string {
@@ -74,3 +79,17 @@ func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Reposito
func IsRepositoryExist(ctx context.Context, repo Repository) (bool, error) {
return util.IsExist(repoPath(repo))
}
// DeleteRepository deletes the repository directory from the disk
func DeleteRepository(ctx context.Context, repo Repository) error {
return util.RemoveAll(repoPath(repo))
}
// RenameRepository renames a repository's name on disk
func RenameRepository(ctx context.Context, repo Repository, newName string) error {
newRepoPath := absPath(repo.GetOwnerName(), newName)
if err := util.Rename(repoPath(repo), newRepoPath); err != nil {
return fmt.Errorf("rename repository directory: %w", err)
}
return nil
}
@@ -1,9 +1,10 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
package gitrepo
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -105,10 +106,18 @@ done
return hookNames, hookTpls, giteaHookTpls
}
// CreateDelegateHooks creates all the hooks scripts for the repo
func CreateDelegateHooks(repoPath string) (err error) {
// CreateDelegateHooksForRepo creates all the hooks scripts for the repo
func CreateDelegateHooksForRepo(_ context.Context, repo Repository) (err error) {
return createDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
}
// CreateDelegateHooksForWiki creates all the hooks scripts for the wiki repo
func CreateDelegateHooksForWiki(_ context.Context, repo Repository) (err error) {
return createDelegateHooks(filepath.Join(wikiPath(repo), "hooks"))
}
func createDelegateHooks(hookDir string) (err error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
hookDir := filepath.Join(repoPath, "hooks")
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
@@ -169,11 +178,19 @@ func ensureExecutable(filename string) error {
return os.Chmod(filename, mode)
}
// CheckDelegateHooks checks the hooks scripts for the repo
func CheckDelegateHooks(repoPath string) ([]string, error) {
// CheckDelegateHooksForRepo checks the hooks scripts for the repo
func CheckDelegateHooksForRepo(_ context.Context, repo Repository) ([]string, error) {
return checkDelegateHooks(filepath.Join(repoPath(repo), "hooks"))
}
// CheckDelegateHooksForWiki checks the hooks scripts for the repo
func CheckDelegateHooksForWiki(_ context.Context, repo Repository) ([]string, error) {
return checkDelegateHooks(filepath.Join(wikiPath(repo), "hooks"))
}
func checkDelegateHooks(hookDir string) ([]string, error) {
hookNames, hookTpls, giteaHookTpls := getHookTemplates()
hookDir := filepath.Join(repoPath, "hooks")
results := make([]string, 0, 10)
for i, hookName := range hookNames {
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitrepo
import (
"context"
"code.gitea.io/gitea/modules/git"
)
// IsTagExist returns true if given tag exists in the repository.
func IsTagExist(ctx context.Context, repo Repository, name string) bool {
return IsReferenceExist(ctx, repo, git.TagPrefix+name)
}
+7 -2
View File
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"github.com/blevesearch/bleve/v2"
analyzer_custom "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
@@ -272,14 +273,18 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
pathQuery.FieldVal = "Filename"
pathQuery.SetBoost(10)
if opts.SearchMode == indexer.SearchModeExact {
searchMode := util.IfZero(opts.SearchMode, b.SupportedSearchModes()[0].ModeValue)
if searchMode == indexer.SearchModeExact {
// 1.21 used NewPrefixQuery, but it seems not working well, and later releases changed to NewMatchPhraseQuery
q := bleve.NewMatchPhraseQuery(opts.Keyword)
q.Analyzer = repoIndexerAnalyzer
q.FieldVal = "Content"
contentQuery = q
} else /* words */ {
q := bleve.NewMatchQuery(opts.Keyword)
q.FieldVal = "Content"
if opts.SearchMode == indexer.SearchModeFuzzy {
q.Analyzer = repoIndexerAnalyzer
if searchMode == indexer.SearchModeFuzzy {
// this logic doesn't seem right, it is only used to pass the test-case `Keyword: "dESCRIPTION"`, which doesn't seem to be a real-life use-case.
q.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
} else {
@@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"github.com/go-enry/go-enry/v2"
"github.com/olivere/elastic/v7"
@@ -365,7 +366,9 @@ func extractAggs(searchResult *elastic.SearchResult) []*internal.SearchResultLan
// Search searches for codes and language stats by given conditions.
func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
var contentQuery elastic.Query
if opts.SearchMode == indexer.SearchModeExact {
searchMode := util.IfZero(opts.SearchMode, b.SupportedSearchModes()[0].ModeValue)
if searchMode == indexer.SearchModeExact {
// 1.21 used NewMultiMatchQuery().Type(esMultiMatchTypePhrasePrefix), but later releases changed to NewMatchPhraseQuery
contentQuery = elastic.NewMatchPhraseQuery("content", opts.Keyword)
} else /* words */ {
contentQuery = elastic.NewMultiMatchQuery("content", opts.Keyword).Type(esMultiMatchTypeBestFields).Operator("and")
+2 -1
View File
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/indexer/code/internal"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
_ "code.gitea.io/gitea/models"
_ "code.gitea.io/gitea/models/actions"
@@ -240,7 +241,7 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
total, res, langs, err := indexer.Search(t.Context(), &internal.SearchOptions{
RepoIDs: kw.RepoIDs,
Keyword: kw.Keyword,
SearchMode: kw.SearchMode,
SearchMode: util.IfZero(kw.SearchMode, indexer_module.SearchModeWords),
Paginator: &db.ListOptions{
Page: 1,
PageSize: 10,
+4 -2
View File
@@ -10,6 +10,7 @@ import (
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/util"
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
@@ -162,9 +163,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
var queries []query.Query
if options.Keyword != "" {
if options.SearchMode == indexer.SearchModeWords || options.SearchMode == indexer.SearchModeFuzzy {
searchMode := util.IfZero(options.SearchMode, b.SupportedSearchModes()[0].ModeValue)
if searchMode == indexer.SearchModeWords || searchMode == indexer.SearchModeFuzzy {
fuzziness := 0
if options.SearchMode == indexer.SearchModeFuzzy {
if searchMode == indexer.SearchModeFuzzy {
fuzziness = inner_bleve.GuessFuzzinessByKeyword(options.Keyword)
}
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
+6 -5
View File
@@ -13,6 +13,7 @@ import (
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_db "code.gitea.io/gitea/modules/indexer/internal/db"
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
@@ -46,7 +47,7 @@ func (i *Indexer) Delete(_ context.Context, _ ...int64) error {
func buildMatchQuery(mode indexer.SearchModeType, colName, keyword string) builder.Cond {
if mode == indexer.SearchModeExact {
return db.BuildCaseInsensitiveLike("issue.name", keyword)
return db.BuildCaseInsensitiveLike(colName, keyword)
}
// match words
@@ -84,16 +85,16 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
repoCond = builder.Eq{"repo_id": options.RepoIDs[0]}
}
subQuery := builder.Select("id").From("issue").Where(repoCond)
searchMode := util.IfZero(options.SearchMode, i.SupportedSearchModes()[0].ModeValue)
cond = builder.Or(
buildMatchQuery(options.SearchMode, "issue.name", options.Keyword),
buildMatchQuery(options.SearchMode, "issue.content", options.Keyword),
buildMatchQuery(searchMode, "issue.name", options.Keyword),
buildMatchQuery(searchMode, "issue.content", options.Keyword),
builder.In("issue.id", builder.Select("issue_id").
From("comment").
Where(builder.And(
builder.Eq{"type": issue_model.CommentTypeComment},
builder.In("issue_id", subQuery),
buildMatchQuery(options.SearchMode, "content", options.Keyword),
buildMatchQuery(searchMode, "content", options.Keyword),
)),
),
)
@@ -14,6 +14,7 @@ import (
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
"code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/util"
"github.com/olivere/elastic/v7"
)
@@ -152,7 +153,8 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
query := elastic.NewBoolQuery()
if options.Keyword != "" {
if options.SearchMode == indexer.SearchModeExact {
searchMode := util.IfZero(options.SearchMode, b.SupportedSearchModes()[0].ModeValue)
if searchMode == indexer.SearchModeExact {
query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(esMultiMatchTypePhrasePrefix))
} else /* words */ {
query.Must(elastic.NewMultiMatchQuery(options.Keyword, "title", "content", "comments").Type(esMultiMatchTypeBestFields).Operator("and"))
+3 -4
View File
@@ -282,7 +282,7 @@ const (
// SearchIssues search issues by options.
func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, error) {
indexer := *globalIndexer.Load()
ix := *globalIndexer.Load()
if opts.Keyword == "" || opts.IsKeywordNumeric() {
// This is a conservative shortcut.
@@ -291,10 +291,9 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err
// So if the user creates an issue and list issues immediately, the issue may not be listed because the indexer needs time to index the issue.
// Even worse, the external indexer like elastic search may not be available for a while,
// and the user may not be able to list issues completely until it is available again.
indexer = db.NewIndexer()
ix = db.NewIndexer()
}
result, err := indexer.Search(ctx, opts)
result, err := ix.Search(ctx, opts)
if err != nil {
return nil, 0, err
}
+5 -3
View File
@@ -82,9 +82,11 @@ func searchIssueWithKeyword(t *testing.T) {
}
for _, test := range tests {
issueIDs, _, err := SearchIssues(t.Context(), &test.opts)
require.NoError(t, err)
assert.Equal(t, test.expectedIDs, issueIDs)
t.Run(test.opts.Keyword, func(t *testing.T) {
issueIDs, _, err := SearchIssues(t.Context(), &test.opts)
require.NoError(t, err)
assert.Equal(t, test.expectedIDs, issueIDs)
})
}
}
+5 -5
View File
@@ -49,10 +49,10 @@ func (db *DBIndexer) Index(id int64) error {
commitID, err := gitRepo.GetBranchCommitID(repo.DefaultBranch)
if err != nil {
if git.IsErrBranchNotExist(err) || git.IsErrNotExist(err) || setting.IsInTesting {
log.Debug("Unable to get commit ID for default branch %s in %s ... skipping this repository", repo.DefaultBranch, repo.RepoPath())
log.Debug("Unable to get commit ID for default branch %s in %s ... skipping this repository", repo.DefaultBranch, repo.FullName())
return nil
}
log.Error("Unable to get commit ID for default branch %s in %s. Error: %v", repo.DefaultBranch, repo.RepoPath(), err)
log.Error("Unable to get commit ID for default branch %s in %s. Error: %v", repo.DefaultBranch, repo.FullName(), err)
return err
}
@@ -65,17 +65,17 @@ func (db *DBIndexer) Index(id int64) error {
stats, err := gitRepo.GetLanguageStats(commitID)
if err != nil {
if !setting.IsInTesting {
log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.FullName(), err)
}
return err
}
err = repo_model.UpdateLanguageStats(ctx, repo, commitID, stats)
if err != nil {
log.Error("Unable to update language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.RepoPath(), err)
log.Error("Unable to update language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.FullName(), err)
return err
}
log.Debug("DBIndexer completed language stats for ID %s for default branch %s in %s. stats count: %d", commitID, repo.DefaultBranch, repo.RepoPath(), len(stats))
log.Debug("DBIndexer completed language stats for ID %s for default branch %s in %s. stats count: %d", commitID, repo.DefaultBranch, repo.FullName(), len(stats))
return nil
}
+7 -6
View File
@@ -10,6 +10,7 @@ import (
"time"
"code.gitea.io/gitea/models/avatars"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
@@ -43,7 +44,7 @@ func NewPushCommits() *PushCommits {
}
// ToAPIPayloadCommit converts a single PushCommit to an api.PayloadCommit object.
func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repoPath, repoLink string, commit *PushCommit) (*api.PayloadCommit, error) {
func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.User, repo *repo_model.Repository, commit *PushCommit) (*api.PayloadCommit, error) {
var err error
authorUsername := ""
author, ok := emailUsers[commit.AuthorEmail]
@@ -70,7 +71,7 @@ func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.U
committerUsername = committer.Name
}
fileStatus, err := git.GetCommitFileStatus(ctx, repoPath, commit.Sha1)
fileStatus, err := git.GetCommitFileStatus(ctx, repo.RepoPath(), commit.Sha1)
if err != nil {
return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %w", commit.Sha1, err)
}
@@ -78,7 +79,7 @@ func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.U
return &api.PayloadCommit{
ID: commit.Sha1,
Message: commit.Message,
URL: fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(commit.Sha1)),
URL: fmt.Sprintf("%s/commit/%s", repo.HTMLURL(), url.PathEscape(commit.Sha1)),
Author: &api.PayloadUser{
Name: commit.AuthorName,
Email: commit.AuthorEmail,
@@ -98,14 +99,14 @@ func ToAPIPayloadCommit(ctx context.Context, emailUsers map[string]*user_model.U
// ToAPIPayloadCommits converts a PushCommits object to api.PayloadCommit format.
// It returns all converted commits and, if provided, the head commit or an error otherwise.
func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLink string) ([]*api.PayloadCommit, *api.PayloadCommit, error) {
func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repo *repo_model.Repository) ([]*api.PayloadCommit, *api.PayloadCommit, error) {
commits := make([]*api.PayloadCommit, len(pc.Commits))
var headCommit *api.PayloadCommit
emailUsers := make(map[string]*user_model.User)
for i, commit := range pc.Commits {
apiCommit, err := ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, commit)
apiCommit, err := ToAPIPayloadCommit(ctx, emailUsers, repo, commit)
if err != nil {
return nil, nil, err
}
@@ -117,7 +118,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repoPath, repoLi
}
if pc.HeadCommit != nil && headCommit == nil {
var err error
headCommit, err = ToAPIPayloadCommit(ctx, emailUsers, repoPath, repoLink, pc.HeadCommit)
headCommit, err = ToAPIPayloadCommit(ctx, emailUsers, repo, pc.HeadCommit)
if err != nil {
return nil, nil, err
}
+5 -5
View File
@@ -50,14 +50,14 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
pushCommits.HeadCommit = &PushCommit{Sha1: "69554a6"}
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16})
payloadCommits, headCommit, err := pushCommits.ToAPIPayloadCommits(git.DefaultContext, repo.RepoPath(), "/user2/repo16")
payloadCommits, headCommit, err := pushCommits.ToAPIPayloadCommits(git.DefaultContext, repo)
assert.NoError(t, err)
assert.Len(t, payloadCommits, 3)
assert.NotNil(t, headCommit)
assert.Equal(t, "69554a6", payloadCommits[0].ID)
assert.Equal(t, "not signed commit", payloadCommits[0].Message)
assert.Equal(t, "/user2/repo16/commit/69554a6", payloadCommits[0].URL)
assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", payloadCommits[0].URL)
assert.Equal(t, "User2", payloadCommits[0].Committer.Name)
assert.Equal(t, "user2", payloadCommits[0].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[0].Author.Name)
@@ -68,7 +68,7 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.Equal(t, "27566bd", payloadCommits[1].ID)
assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message)
assert.Equal(t, "/user2/repo16/commit/27566bd", payloadCommits[1].URL)
assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/27566bd", payloadCommits[1].URL)
assert.Equal(t, "User2", payloadCommits[1].Committer.Name)
assert.Equal(t, "user2", payloadCommits[1].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[1].Author.Name)
@@ -79,7 +79,7 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.Equal(t, "5099b81", payloadCommits[2].ID)
assert.Equal(t, "good signed commit", payloadCommits[2].Message)
assert.Equal(t, "/user2/repo16/commit/5099b81", payloadCommits[2].URL)
assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/5099b81", payloadCommits[2].URL)
assert.Equal(t, "User2", payloadCommits[2].Committer.Name)
assert.Equal(t, "user2", payloadCommits[2].Committer.UserName)
assert.Equal(t, "User2", payloadCommits[2].Author.Name)
@@ -90,7 +90,7 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.Equal(t, "69554a6", headCommit.ID)
assert.Equal(t, "not signed commit", headCommit.Message)
assert.Equal(t, "/user2/repo16/commit/69554a6", headCommit.URL)
assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", headCommit.URL)
assert.Equal(t, "User2", headCommit.Committer.Name)
assert.Equal(t, "user2", headCommit.Committer.UserName)
assert.Equal(t, "User2", headCommit.Author.Name)
+1 -1
View File
@@ -138,7 +138,7 @@ func CheckInitRepository(ctx context.Context, repo *repo_model.Repository) (err
// Init git bare new repository.
if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil {
return fmt.Errorf("git.InitRepository: %w", err)
} else if err = CreateDelegateHooks(repo.RepoPath()); err != nil {
} else if err = gitrepo.CreateDelegateHooksForRepo(ctx, repo); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
}
return nil
+7
View File
@@ -10,6 +10,8 @@ import "time"
type Secret struct {
// the secret's name
Name string `json:"name"`
// the secret's description
Description string `json:"description"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
}
@@ -21,4 +23,9 @@ type CreateOrUpdateSecretOption struct {
//
// required: true
Data string `json:"data" binding:"Required"`
// Description of the secret to update
//
// required: false
Description string `json:"description"`
}
+12
View File
@@ -10,6 +10,11 @@ type CreateVariableOption struct {
//
// required: true
Value string `json:"value" binding:"Required"`
// Description of the variable to create
//
// required: false
Description string `json:"description"`
}
// UpdateVariableOption the option when updating variable
@@ -21,6 +26,11 @@ type UpdateVariableOption struct {
//
// required: true
Value string `json:"value" binding:"Required"`
// Description of the variable to update
//
// required: false
Description string `json:"description"`
}
// ActionVariable return value of the query API
@@ -34,4 +44,6 @@ type ActionVariable struct {
Name string `json:"name"`
// the value of the variable
Data string `json:"data"`
// the description of the variable
Description string `json:"description"`
}