0
0
Fork 0
mirror of https://github.com/go-gitea/gitea synced 2024-11-29 13:37:52 +01:00

Merge branch 'main' into github-like-repo-home-page

This commit is contained in:
yp05327 2024-10-21 10:22:10 +09:00 committed by GitHub
commit 69f86015ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
155 changed files with 2436 additions and 605 deletions

View file

@ -593,6 +593,7 @@ Gitea or set your environment appropriately.`, "")
hookOptions := private.HookOptions{ hookOptions := private.HookOptions{
UserName: pusherName, UserName: pusherName,
UserID: pusherID, UserID: pusherID,
GitPushOptions: make(map[string]string),
} }
hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
@ -617,8 +618,6 @@ Gitea or set your environment appropriately.`, "")
hookOptions.RefFullNames = append(hookOptions.RefFullNames, git.RefName(t[2])) hookOptions.RefFullNames = append(hookOptions.RefFullNames, git.RefName(t[2]))
} }
hookOptions.GitPushOptions = make(map[string]string)
if hasPushOptions { if hasPushOptions {
for { for {
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow) rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
@ -629,11 +628,7 @@ Gitea or set your environment appropriately.`, "")
if rs.Type == pktLineTypeFlush { if rs.Type == pktLineTypeFlush {
break break
} }
hookOptions.GitPushOptions.AddFromKeyValue(string(rs.Data))
kv := strings.SplitN(string(rs.Data), "=", 2)
if len(kv) == 2 {
hookOptions.GitPushOptions[kv[0]] = kv[1]
}
} }
} }

View file

@ -64,6 +64,11 @@ func main() {
Value: "", Value: "",
Usage: "Forked user name on Github", Usage: "Forked user name on Github",
}, },
&cli.StringFlag{
Name: "gh-access-token",
Value: "",
Usage: "Access token for GitHub api request",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "no-fetch", Name: "no-fetch",
Usage: "Set this flag to prevent fetch of remote branches", Usage: "Set this flag to prevent fetch of remote branches",
@ -169,9 +174,10 @@ func runBackport(c *cli.Context) error {
fmt.Printf("* Backporting %s to %s as %s\n", pr, localReleaseBranch, backportBranch) fmt.Printf("* Backporting %s to %s as %s\n", pr, localReleaseBranch, backportBranch)
sha := c.String("cherry-pick") sha := c.String("cherry-pick")
accessToken := c.String("gh-access-token")
if sha == "" { if sha == "" {
var err error var err error
sha, err = determineSHAforPR(ctx, pr) sha, err = determineSHAforPR(ctx, pr, accessToken)
if err != nil { if err != nil {
return err return err
} }
@ -427,13 +433,16 @@ func readVersion() string {
return strings.Join(split[:2], ".") return strings.Join(split[:2], ".")
} }
func determineSHAforPR(ctx context.Context, prStr string) (string, error) { func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string, error) {
prNum, err := strconv.Atoi(prStr) prNum, err := strconv.Atoi(prStr)
if err != nil { if err != nil {
return "", err return "", err
} }
client := github.NewClient(http.DefaultClient) client := github.NewClient(http.DefaultClient)
if accessToken != "" {
client = client.WithAuthToken(accessToken)
}
pr, _, err := client.PullRequests.Get(ctx, "go-gitea", "gitea", prNum) pr, _, err := client.PullRequests.Get(ctx, "go-gitea", "gitea", prNum)
if err != nil { if err != nil {

2
go.mod
View file

@ -54,7 +54,7 @@ require (
github.com/go-chi/chi/v5 v5.0.13 github.com/go-chi/chi/v5 v5.0.13
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.8.8 github.com/go-enry/go-enry/v2 v2.9.1
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6

4
go.sum
View file

@ -315,8 +315,8 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-enry/go-enry/v2 v2.8.8 h1:EhfxWpw4DQ3WEFB1Y77X8vKqZL0D0EDUUWYDUAIv9/4= github.com/go-enry/go-enry/v2 v2.9.1 h1:G9iDteJ/Mc0F4Di5NeQknf83R2OkRbwY9cAYmcqVG6U=
github.com/go-enry/go-enry/v2 v2.8.8/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-enry/v2 v2.9.1/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=

View file

@ -69,7 +69,7 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
OwnerID: t.OwnerID, OwnerID: t.OwnerID,
CommitSHA: t.CommitSHA, CommitSHA: t.CommitSHA,
Status: int64(ArtifactStatusUploadPending), Status: int64(ArtifactStatusUploadPending),
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + 3600*24*expiredDays), ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
} }
if _, err := db.GetEngine(ctx).Insert(artifact); err != nil { if _, err := db.GetEngine(ctx).Insert(artifact); err != nil {
return nil, err return nil, err
@ -78,6 +78,13 @@ func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPa
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
if _, err := db.GetEngine(ctx).ID(artifact.ID).Cols("expired_unix").Update(&ActionArtifact{
ExpiredUnix: timeutil.TimeStamp(time.Now().Unix() + timeutil.Day*expiredDays),
}); err != nil {
return nil, err
}
return artifact, nil return artifact, nil
} }

View file

@ -712,3 +712,24 @@
type: 3 type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}" config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810 created_unix: 946684810
-
id: 108
repo_id: 62
type: 1
config: "{}"
created_unix: 946684810
-
id: 109
repo_id: 62
type: 2
config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}"
created_unix: 946684810
-
id: 110
repo_id: 62
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810

View file

@ -1768,3 +1768,34 @@
size: 0 size: 0
is_fsck_enabled: true is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false close_issues_via_commit_in_any_branch: false
-
id: 62
owner_id: 42
owner_name: org42
lower_name: search-by-path
name: search-by-path
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
num_milestones: 0
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 0
is_private: false
is_empty: false
is_archived: false
is_mirror: false
status: 0
is_fork: false
fork_id: 0
is_template: false
template_id: 0
size: 0
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false

View file

@ -1517,3 +1517,40 @@
repo_admin_change_team_access: false repo_admin_change_team_access: false
theme: "" theme: ""
keep_activity_private: false keep_activity_private: false
-
id: 42
lower_name: org42
name: org42
full_name: Org42
email: org42@example.com
keep_email_private: false
email_notifications_preference: onmention
passwd: ZogKvWdyEx:password
passwd_hash_algo: dummy
must_change_password: false
login_source: 0
login_name: org42
type: 1
salt: ZogKvWdyEx
max_repo_creation: -1
is_active: false
is_admin: false
is_restricted: false
allow_git_hook: false
allow_import_local: false
allow_create_organization: true
prohibit_login: false
avatar: avatar42
avatar_email: org42@example.com
use_custom_avatar: false
num_followers: 0
num_following: 0
num_stars: 0
num_repos: 1
num_teams: 0
num_members: 0
visibility: 0
repo_admin_change_team_access: false
theme: ""
keep_activity_private: false

View file

@ -138,12 +138,12 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
count: 33, count: 34,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
count: 38, count: 39,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
@ -158,7 +158,7 @@ func getTestCases() []struct {
{ {
name: "AllPublic/PublicRepositoriesOfOrganization", name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
count: 33, count: 34,
}, },
{ {
name: "AllTemplates", name: "AllTemplates",

View file

@ -408,6 +408,10 @@ func (u *User) IsIndividual() bool {
return u.Type == UserTypeIndividual return u.Type == UserTypeIndividual
} }
func (u *User) IsUser() bool {
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
}
// IsBot returns whether or not the user is of type bot // IsBot returns whether or not the user is of type bot
func (u *User) IsBot() bool { func (u *User) IsBot() bool {
return u.Type == UserTypeBot return u.Type == UserTypeBot
@ -561,42 +565,43 @@ var (
".", ".",
"..", "..",
".well-known", ".well-known",
"admin",
"api", "api", // gitea api
"assets", "metrics", // prometheus metrics api
"attachments", "v2", // container registry api
"avatar",
"avatars", "assets", // static asset files
"captcha", "attachments", // issue attachments
"commits",
"debug", "avatar", // avatar by email hash
"error", "avatars", // user avatars by file name
"explore",
"favicon.ico",
"ghost",
"issues",
"login",
"manifest.json",
"metrics",
"milestones",
"new",
"notifications",
"org",
"pulls",
"raw",
"repo",
"repo-avatars", "repo-avatars",
"robots.txt",
"search", "captcha",
"serviceworker.js", "login", // oauth2 login
"ssh_info", "org", // org create/manage, or "/org/{org}", BUT if an org is named as "invite" then it goes wrong
"repo", // repo create/migrate, etc
"user", // user login/activate/settings, etc
"explore",
"issues",
"pulls",
"milestones",
"notifications",
"favicon.ico",
"manifest.json", // web app manifests
"robots.txt", // search engine robots
"sitemap.xml", // search engine sitemap
"ssh_info", // agit info
"swagger.v1.json", "swagger.v1.json",
"user",
"v2", "ghost", // reserved name for deleted users (id: -1)
"gitea-actions", "gitea-actions", // gitea builtin user (id: -2)
} }
// DON'T ADD ANY NEW STUFF, WE SOLVE THIS WITH `/user/{obj}` PATHS! // These names are reserved for user accounts: user's keys, user's rss feed, user's avatar, etc.
// DO NOT add any new stuff! The paths with these names are processed by `/{username}` handler (UsernameSubRoute) manually.
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"} reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
) )

View file

@ -92,7 +92,10 @@ func TestSearchUsers(t *testing.T) {
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
[]int64{26, 41}) []int64{26, 41})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}},
[]int64{42})
testOrgSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}},
[]int64{}) []int64{})
// test users // test users

View file

@ -111,12 +111,12 @@ func SetExecutablePath(path string) error {
func ensureGitVersion() error { func ensureGitVersion() error {
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) { if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
moreHint := "get git: https://git-scm.com/download/" moreHint := "get git: https://git-scm.com/downloads"
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them // there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
if _, err := os.Stat("/etc/redhat-release"); err == nil { if _, err := os.Stat("/etc/redhat-release"); err == nil {
// ius.io is the recommended official(git-scm.com) method to install git // ius.io is the recommended official(git-scm.com) method to install git
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io" moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
} }
} }
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint) return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)

View file

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal" indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve" inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
@ -53,6 +54,7 @@ type RepoIndexerData struct {
RepoID int64 RepoID int64
CommitID string CommitID string
Content string Content string
Filename string
Language string Language string
UpdatedAt time.Time UpdatedAt time.Time
} }
@ -64,8 +66,10 @@ func (d *RepoIndexerData) Type() string {
const ( const (
repoIndexerAnalyzer = "repoIndexerAnalyzer" repoIndexerAnalyzer = "repoIndexerAnalyzer"
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
filenameIndexerTokenizer = "filenameIndexerTokenizer"
repoIndexerDocType = "repoIndexerDocType" repoIndexerDocType = "repoIndexerDocType"
repoIndexerLatestVersion = 6 repoIndexerLatestVersion = 7
) )
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer // generateBleveIndexMapping generates a bleve index mapping for the repo indexer
@ -79,6 +83,11 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
textFieldMapping.IncludeInAll = false textFieldMapping.IncludeInAll = false
docMapping.AddFieldMappingsAt("Content", textFieldMapping) docMapping.AddFieldMappingsAt("Content", textFieldMapping)
fileNamedMapping := bleve.NewTextFieldMapping()
fileNamedMapping.IncludeInAll = false
fileNamedMapping.Analyzer = filenameIndexerAnalyzer
docMapping.AddFieldMappingsAt("Filename", fileNamedMapping)
termFieldMapping := bleve.NewTextFieldMapping() termFieldMapping := bleve.NewTextFieldMapping()
termFieldMapping.IncludeInAll = false termFieldMapping.IncludeInAll = false
termFieldMapping.Analyzer = analyzer_keyword.Name termFieldMapping.Analyzer = analyzer_keyword.Name
@ -90,6 +99,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
docMapping.AddFieldMappingsAt("UpdatedAt", timeFieldMapping) docMapping.AddFieldMappingsAt("UpdatedAt", timeFieldMapping)
mapping := bleve.NewIndexMapping() mapping := bleve.NewIndexMapping()
if err := addUnicodeNormalizeTokenFilter(mapping); err != nil { if err := addUnicodeNormalizeTokenFilter(mapping); err != nil {
return nil, err return nil, err
} else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{ } else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{
@ -100,6 +110,16 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
if err := mapping.AddCustomAnalyzer(filenameIndexerAnalyzer, map[string]any{
"type": analyzer_custom.Name,
"char_filters": []string{},
"tokenizer": unicode.Name,
"token_filters": []string{unicodeNormalizeName, path_filter.Name, lowercase.Name},
}); err != nil {
return nil, err
}
mapping.DefaultAnalyzer = repoIndexerAnalyzer mapping.DefaultAnalyzer = repoIndexerAnalyzer
mapping.AddDocumentMapping(repoIndexerDocType, docMapping) mapping.AddDocumentMapping(repoIndexerDocType, docMapping)
mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping()) mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping())
@ -174,6 +194,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
return batch.Index(id, &RepoIndexerData{ return batch.Index(id, &RepoIndexerData{
RepoID: repo.ID, RepoID: repo.ID,
CommitID: commitSha, CommitID: commitSha,
Filename: update.Filename,
Content: string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})), Content: string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
Language: analyze.GetCodeLanguage(update.Filename, fileContents), Language: analyze.GetCodeLanguage(update.Filename, fileContents),
UpdatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
@ -240,14 +261,19 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
keywordQuery query.Query keywordQuery query.Query
) )
phraseQuery := bleve.NewMatchPhraseQuery(opts.Keyword) pathQuery := bleve.NewPrefixQuery(strings.ToLower(opts.Keyword))
phraseQuery.FieldVal = "Content" pathQuery.FieldVal = "Filename"
phraseQuery.Analyzer = repoIndexerAnalyzer pathQuery.SetBoost(10)
keywordQuery = phraseQuery
contentQuery := bleve.NewMatchQuery(opts.Keyword)
contentQuery.FieldVal = "Content"
if opts.IsKeywordFuzzy { if opts.IsKeywordFuzzy {
phraseQuery.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword) contentQuery.Fuzziness = inner_bleve.GuessFuzzinessByKeyword(opts.Keyword)
} }
keywordQuery = bleve.NewDisjunctionQuery(contentQuery, pathQuery)
if len(opts.RepoIDs) > 0 { if len(opts.RepoIDs) > 0 {
repoQueries := make([]query.Query, 0, len(opts.RepoIDs)) repoQueries := make([]query.Query, 0, len(opts.RepoIDs))
for _, repoID := range opts.RepoIDs { for _, repoID := range opts.RepoIDs {
@ -277,7 +303,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
from, pageSize := opts.GetSkipTake() from, pageSize := opts.GetSkipTake()
searchRequest := bleve.NewSearchRequestOptions(indexerQuery, pageSize, from, false) searchRequest := bleve.NewSearchRequestOptions(indexerQuery, pageSize, from, false)
searchRequest.Fields = []string{"Content", "RepoID", "Language", "CommitID", "UpdatedAt"} searchRequest.Fields = []string{"Content", "Filename", "RepoID", "Language", "CommitID", "UpdatedAt"}
searchRequest.IncludeLocations = true searchRequest.IncludeLocations = true
if len(opts.Language) == 0 { if len(opts.Language) == 0 {
@ -307,6 +333,10 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
endIndex = locationEnd endIndex = locationEnd
} }
} }
if len(hit.Locations["Filename"]) > 0 {
startIndex, endIndex = internal.FilenameMatchIndexPos(hit.Fields["Content"].(string))
}
language := hit.Fields["Language"].(string) language := hit.Fields["Language"].(string)
var updatedUnix timeutil.TimeStamp var updatedUnix timeutil.TimeStamp
if t, err := time.Parse(time.RFC3339, hit.Fields["UpdatedAt"].(string)); err == nil { if t, err := time.Parse(time.RFC3339, hit.Fields["UpdatedAt"].(string)); err == nil {

View file

@ -0,0 +1,101 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package path
import (
"slices"
"strings"
"github.com/blevesearch/bleve/v2/analysis"
"github.com/blevesearch/bleve/v2/registry"
)
const (
Name = "gitea/path"
)
type TokenFilter struct{}
func NewTokenFilter() *TokenFilter {
return &TokenFilter{}
}
func TokenFilterConstructor(config map[string]any, cache *registry.Cache) (analysis.TokenFilter, error) {
return NewTokenFilter(), nil
}
func (s *TokenFilter) Filter(input analysis.TokenStream) analysis.TokenStream {
if len(input) == 1 {
// if there is only one token, we dont need to generate the reversed chain
return generatePathTokens(input, false)
}
normal := generatePathTokens(input, false)
reversed := generatePathTokens(input, true)
return append(normal, reversed...)
}
// Generates path tokens from the input tokens.
// This mimics the behavior of the path hierarchy tokenizer in ES. It takes the input tokens and combine them, generating a term for each component
// in tree (e.g., foo/bar/baz.md will generate foo, foo/bar, and foo/bar/baz.md).
//
// If the reverse flag is set, the order of the tokens is reversed (the same input will generate baz.md, baz.md/bar, baz.md/bar/foo). This is useful
// to efficiently search for filenames without supplying the fullpath.
func generatePathTokens(input analysis.TokenStream, reversed bool) analysis.TokenStream {
terms := make([]string, 0, len(input))
longestTerm := 0
if reversed {
slices.Reverse(input)
}
for i := 0; i < len(input); i++ {
var sb strings.Builder
sb.WriteString(string(input[0].Term))
for j := 1; j < i; j++ {
sb.WriteString("/")
sb.WriteString(string(input[j].Term))
}
term := sb.String()
if longestTerm < len(term) {
longestTerm = len(term)
}
terms = append(terms, term)
}
output := make(analysis.TokenStream, 0, len(terms))
for _, term := range terms {
var start, end int
if reversed {
start = 0
end = len(term)
} else {
start = longestTerm - len(term)
end = longestTerm
}
token := analysis.Token{
Position: 1,
Start: start,
End: end,
Type: analysis.AlphaNumeric,
Term: []byte(term),
}
output = append(output, &token)
}
return output
}
func init() {
registry.RegisterTokenFilter(Name, TokenFilterConstructor)
}

View file

@ -0,0 +1,76 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package path
import (
"fmt"
"testing"
"github.com/blevesearch/bleve/v2/analysis"
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
"github.com/stretchr/testify/assert"
)
type Scenario struct {
Input string
Tokens []string
}
func TestTokenFilter(t *testing.T) {
scenarios := []struct {
Input string
Terms []string
}{
{
Input: "Dockerfile",
Terms: []string{"Dockerfile"},
},
{
Input: "Dockerfile.rootless",
Terms: []string{"Dockerfile.rootless"},
},
{
Input: "a/b/c/Dockerfile.rootless",
Terms: []string{"a", "a/b", "a/b/c", "a/b/c/Dockerfile.rootless", "Dockerfile.rootless", "Dockerfile.rootless/c", "Dockerfile.rootless/c/b", "Dockerfile.rootless/c/b/a"},
},
{
Input: "",
Terms: []string{},
},
}
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("ensure terms of '%s'", scenario.Input), func(t *testing.T) {
terms := extractTerms(scenario.Input)
assert.Len(t, terms, len(scenario.Terms))
for _, term := range terms {
assert.Contains(t, scenario.Terms, term)
}
})
}
}
func extractTerms(input string) []string {
tokens := tokenize(input)
filteredTokens := filter(tokens)
terms := make([]string, 0, len(filteredTokens))
for _, token := range filteredTokens {
terms = append(terms, string(token.Term))
}
return terms
}
func filter(input analysis.TokenStream) analysis.TokenStream {
filter := NewTokenFilter()
return filter.Filter(input)
}
func tokenize(input string) analysis.TokenStream {
tokenizer := unicode.NewUnicodeTokenizer()
return tokenizer.Tokenize([]byte(input))
}

View file

@ -30,7 +30,7 @@ import (
) )
const ( const (
esRepoIndexerLatestVersion = 1 esRepoIndexerLatestVersion = 2
// multi-match-types, currently only 2 types are used // multi-match-types, currently only 2 types are used
// Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types // Reference: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html#multi-match-types
esMultiMatchTypeBestFields = "best_fields" esMultiMatchTypeBestFields = "best_fields"
@ -57,12 +57,50 @@ func NewIndexer(url, indexerName string) *Indexer {
const ( const (
defaultMapping = `{ defaultMapping = `{
"settings": {
"analysis": {
"analyzer": {
"filename_path_analyzer": {
"tokenizer": "path_tokenizer"
},
"reversed_filename_path_analyzer": {
"tokenizer": "reversed_path_tokenizer"
}
},
"tokenizer": {
"path_tokenizer": {
"type": "path_hierarchy",
"delimiter": "/"
},
"reversed_path_tokenizer": {
"type": "path_hierarchy",
"delimiter": "/",
"reverse": true
}
}
}
},
"mappings": { "mappings": {
"properties": { "properties": {
"repo_id": { "repo_id": {
"type": "long", "type": "long",
"index": true "index": true
}, },
"filename": {
"type": "text",
"term_vector": "with_positions_offsets",
"index": true,
"fields": {
"path": {
"type": "text",
"analyzer": "reversed_filename_path_analyzer"
},
"path_reversed": {
"type": "text",
"analyzer": "filename_path_analyzer"
}
}
},
"content": { "content": {
"type": "text", "type": "text",
"term_vector": "with_positions_offsets", "term_vector": "with_positions_offsets",
@ -136,6 +174,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
Id(id). Id(id).
Doc(map[string]any{ Doc(map[string]any{
"repo_id": repo.ID, "repo_id": repo.ID,
"filename": update.Filename,
"content": string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})), "content": string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
"commit_id": sha, "commit_id": sha,
"language": analyze.GetCodeLanguage(update.Filename, fileContents), "language": analyze.GetCodeLanguage(update.Filename, fileContents),
@ -231,11 +270,11 @@ func (b *Indexer) doDelete(ctx context.Context, repoID int64) error {
return err return err
} }
// indexPos find words positions for start and the following end on content. It will // contentMatchIndexPos find words positions for start and the following end on content. It will
// return the beginning position of the first start and the ending position of the // return the beginning position of the first start and the ending position of the
// first end following the start string. // first end following the start string.
// If not found any of the positions, it will return -1, -1. // If not found any of the positions, it will return -1, -1.
func indexPos(content, start, end string) (int, int) { func contentMatchIndexPos(content, start, end string) (int, int) {
startIdx := strings.Index(content, start) startIdx := strings.Index(content, start)
if startIdx < 0 { if startIdx < 0 {
return -1, -1 return -1, -1
@ -244,22 +283,29 @@ func indexPos(content, start, end string) (int, int) {
if endIdx < 0 { if endIdx < 0 {
return -1, -1 return -1, -1
} }
return startIdx, startIdx + len(start) + endIdx + len(end) return startIdx, (startIdx + len(start) + endIdx + len(end)) - 9 // remove the length <em></em> since we give Content the original data
} }
func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) { func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int) (int64, []*internal.SearchResult, []*internal.SearchResultLanguages, error) {
hits := make([]*internal.SearchResult, 0, pageSize) hits := make([]*internal.SearchResult, 0, pageSize)
for _, hit := range searchResult.Hits.Hits { for _, hit := range searchResult.Hits.Hits {
repoID, fileName := internal.ParseIndexerID(hit.Id)
res := make(map[string]any)
if err := json.Unmarshal(hit.Source, &res); err != nil {
return 0, nil, nil, err
}
// FIXME: There is no way to get the position the keyword on the content currently on the same request. // FIXME: There is no way to get the position the keyword on the content currently on the same request.
// So we get it from content, this may made the query slower. See // So we get it from content, this may made the query slower. See
// https://discuss.elastic.co/t/fetching-position-of-keyword-in-matched-document/94291 // https://discuss.elastic.co/t/fetching-position-of-keyword-in-matched-document/94291
var startIndex, endIndex int var startIndex, endIndex int
c, ok := hit.Highlight["content"] if c, ok := hit.Highlight["filename"]; ok && len(c) > 0 {
if ok && len(c) > 0 { startIndex, endIndex = internal.FilenameMatchIndexPos(res["content"].(string))
} else if c, ok := hit.Highlight["content"]; ok && len(c) > 0 {
// FIXME: Since the highlighting content will include <em> and </em> for the keywords, // FIXME: Since the highlighting content will include <em> and </em> for the keywords,
// now we should find the positions. But how to avoid html content which contains the // now we should find the positions. But how to avoid html content which contains the
// <em> and </em> tags? If elastic search has handled that? // <em> and </em> tags? If elastic search has handled that?
startIndex, endIndex = indexPos(c[0], "<em>", "</em>") startIndex, endIndex = contentMatchIndexPos(c[0], "<em>", "</em>")
if startIndex == -1 { if startIndex == -1 {
panic(fmt.Sprintf("1===%s,,,%#v,,,%s", kw, hit.Highlight, c[0])) panic(fmt.Sprintf("1===%s,,,%#v,,,%s", kw, hit.Highlight, c[0]))
} }
@ -267,12 +313,6 @@ func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int)
panic(fmt.Sprintf("2===%#v", hit.Highlight)) panic(fmt.Sprintf("2===%#v", hit.Highlight))
} }
repoID, fileName := internal.ParseIndexerID(hit.Id)
res := make(map[string]any)
if err := json.Unmarshal(hit.Source, &res); err != nil {
return 0, nil, nil, err
}
language := res["language"].(string) language := res["language"].(string)
hits = append(hits, &internal.SearchResult{ hits = append(hits, &internal.SearchResult{
@ -283,7 +323,7 @@ func convertResult(searchResult *elastic.SearchResult, kw string, pageSize int)
UpdatedUnix: timeutil.TimeStamp(res["updated_at"].(float64)), UpdatedUnix: timeutil.TimeStamp(res["updated_at"].(float64)),
Language: language, Language: language,
StartIndex: startIndex, StartIndex: startIndex,
EndIndex: endIndex - 9, // remove the length <em></em> since we give Content the original data EndIndex: endIndex,
Color: enry.GetColor(language), Color: enry.GetColor(language),
}) })
} }
@ -315,7 +355,10 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
searchType = esMultiMatchTypeBestFields searchType = esMultiMatchTypeBestFields
} }
kwQuery := elastic.NewMultiMatchQuery(opts.Keyword, "content").Type(searchType) kwQuery := elastic.NewBoolQuery().Should(
elastic.NewMultiMatchQuery(opts.Keyword, "content").Type(searchType),
elastic.NewMultiMatchQuery(opts.Keyword, "filename^10").Type(esMultiMatchTypePhrasePrefix),
)
query := elastic.NewBoolQuery() query := elastic.NewBoolQuery()
query = query.Must(kwQuery) query = query.Must(kwQuery)
if len(opts.RepoIDs) > 0 { if len(opts.RepoIDs) > 0 {
@ -341,6 +384,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
Highlight( Highlight(
elastic.NewHighlight(). elastic.NewHighlight().
Field("content"). Field("content").
Field("filename").
NumOfFragments(0). // return all highting content on fragments NumOfFragments(0). // return all highting content on fragments
HighlighterType("fvh"), HighlighterType("fvh"),
). ).
@ -373,6 +417,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
Highlight( Highlight(
elastic.NewHighlight(). elastic.NewHighlight().
Field("content"). Field("content").
Field("filename").
NumOfFragments(0). // return all highting content on fragments NumOfFragments(0). // return all highting content on fragments
HighlighterType("fvh"), HighlighterType("fvh"),
). ).

View file

@ -10,7 +10,7 @@ import (
) )
func TestIndexPos(t *testing.T) { func TestIndexPos(t *testing.T) {
startIdx, endIdx := indexPos("test index start and end", "start", "end") startIdx, endIdx := contentMatchIndexPos("test index start and end", "start", "end")
assert.EqualValues(t, 11, startIdx) assert.EqualValues(t, 11, startIdx)
assert.EqualValues(t, 24, endIdx) assert.EqualValues(t, 15, endIdx)
} }

View file

@ -6,6 +6,7 @@ package code
import ( import (
"context" "context"
"os" "os"
"slices"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -20,53 +21,166 @@ import (
_ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/activities"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
_ "github.com/mattn/go-sqlite3"
) )
type codeSearchResult struct {
Filename string
Content string
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
unittest.MainTest(m) unittest.MainTest(m)
} }
func testIndexer(name string, t *testing.T, indexer internal.Indexer) { func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
var repoID int64 = 1 assert.NoError(t, setupRepositoryIndexes(git.DefaultContext, indexer))
err := index(git.DefaultContext, indexer, repoID)
assert.NoError(t, err)
keywords := []struct { keywords := []struct {
RepoIDs []int64 RepoIDs []int64
Keyword string Keyword string
IDs []int64
Langs int Langs int
Results []codeSearchResult
}{ }{
// Search for an exact match on the contents of a file
// This scenario yields a single result (the file README.md on the repo '1')
{ {
RepoIDs: nil, RepoIDs: nil,
Keyword: "Description", Keyword: "Description",
IDs: []int64{repoID},
Langs: 1, Langs: 1,
Results: []codeSearchResult{
{
Filename: "README.md",
Content: "# repo1\n\nDescription for repo1",
}, },
},
},
// Search for an exact match on the contents of a file within the repo '2'.
// This scenario yields no results
{ {
RepoIDs: []int64{2}, RepoIDs: []int64{2},
Keyword: "Description", Keyword: "Description",
IDs: []int64{},
Langs: 0, Langs: 0,
}, },
// Search for an exact match on the contents of a file
// This scenario yields a single result (the file README.md on the repo '1')
{ {
RepoIDs: nil, RepoIDs: nil,
Keyword: "repo1", Keyword: "repo1",
IDs: []int64{repoID},
Langs: 1, Langs: 1,
Results: []codeSearchResult{
{
Filename: "README.md",
Content: "# repo1\n\nDescription for repo1",
}, },
},
},
// Search for an exact match on the contents of a file within the repo '2'.
// This scenario yields no results
{ {
RepoIDs: []int64{2}, RepoIDs: []int64{2},
Keyword: "repo1", Keyword: "repo1",
IDs: []int64{},
Langs: 0, Langs: 0,
}, },
// Search for a non-existing term.
// This scenario yields no results
{ {
RepoIDs: nil, RepoIDs: nil,
Keyword: "non-exist", Keyword: "non-exist",
IDs: []int64{},
Langs: 0, Langs: 0,
}, },
// Search for an exact match on the contents of a file within the repo '62'.
// This scenario yields a single result (the file avocado.md on the repo '62')
{
RepoIDs: []int64{62},
Keyword: "pineaple",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "avocado.md",
Content: "# repo1\n\npineaple pie of cucumber juice",
},
},
},
// Search for an exact match on the filename within the repo '62'.
// This scenario yields a single result (the file avocado.md on the repo '62')
{
RepoIDs: []int64{62},
Keyword: "avocado.md",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "avocado.md",
Content: "# repo1\n\npineaple pie of cucumber juice",
},
},
},
// Search for an partial match on the filename within the repo '62'.
// This scenario yields a single result (the file avocado.md on the repo '62')
{
RepoIDs: []int64{62},
Keyword: "avo",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "avocado.md",
Content: "# repo1\n\npineaple pie of cucumber juice",
},
},
},
// Search for matches on both the contents and the filenames within the repo '62'.
// This scenario yields two results: the first result is baed on the file (cucumber.md) while the second is based on the contents
{
RepoIDs: []int64{62},
Keyword: "cucumber",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "cucumber.md",
Content: "Salad is good for your health",
},
{
Filename: "avocado.md",
Content: "# repo1\n\npineaple pie of cucumber juice",
},
},
},
// Search for matches on the filenames within the repo '62'.
// This scenario yields two results (both are based on filename, the first one is an exact match)
{
RepoIDs: []int64{62},
Keyword: "ham",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "ham.md",
Content: "This is also not cheese",
},
{
Filename: "potato/ham.md",
Content: "This is not cheese",
},
},
},
// Search for matches on the contents of files within the repo '62'.
// This scenario yields two results (both are based on contents, the first one is an exact match where as the second is a 'fuzzy' one)
{
RepoIDs: []int64{62},
Keyword: "This is not cheese",
Langs: 1,
Results: []codeSearchResult{
{
Filename: "potato/ham.md",
Content: "This is not cheese",
},
{
Filename: "ham.md",
Content: "This is also not cheese",
},
},
},
} }
for _, kw := range keywords { for _, kw := range keywords {
@ -81,19 +195,37 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
IsKeywordFuzzy: true, IsKeywordFuzzy: true,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, kw.IDs, int(total))
assert.Len(t, langs, kw.Langs) assert.Len(t, langs, kw.Langs)
ids := make([]int64, 0, len(res)) hits := make([]codeSearchResult, 0, len(res))
for _, hit := range res {
ids = append(ids, hit.RepoID) if total > 0 {
assert.EqualValues(t, "# repo1\n\nDescription for repo1", hit.Content) assert.NotEmpty(t, kw.Results, "The given scenario does not provide any expected results")
} }
assert.EqualValues(t, kw.IDs, ids)
for _, hit := range res {
hits = append(hits, codeSearchResult{
Filename: hit.Filename,
Content: hit.Content,
}) })
} }
assert.NoError(t, indexer.Delete(context.Background(), repoID)) lastIndex := -1
for _, expected := range kw.Results {
index := slices.Index(hits, expected)
if index == -1 {
assert.Failf(t, "Result not found", "Expected %v in %v", expected, hits)
} else if lastIndex > index {
assert.Failf(t, "Result is out of order", "The order of %v within %v is wrong", expected, hits)
} else {
lastIndex = index
}
}
})
}
assert.NoError(t, tearDownRepositoryIndexes(indexer))
}) })
} }
@ -136,3 +268,25 @@ func TestESIndexAndSearch(t *testing.T) {
testIndexer("elastic_search", t, indexer) testIndexer("elastic_search", t, indexer)
} }
func setupRepositoryIndexes(ctx context.Context, indexer internal.Indexer) error {
for _, repoID := range repositoriesToSearch() {
if err := index(ctx, indexer, repoID); err != nil {
return err
}
}
return nil
}
func tearDownRepositoryIndexes(indexer internal.Indexer) error {
for _, repoID := range repositoriesToSearch() {
if err := indexer.Delete(context.Background(), repoID); err != nil {
return err
}
}
return nil
}
func repositoriesToSearch() []int64 {
return []int64{1, 62}
}

View file

@ -10,6 +10,10 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
) )
const (
filenameMatchNumberOfLines = 7 // Copied from github search
)
func FilenameIndexerID(repoID int64, filename string) string { func FilenameIndexerID(repoID int64, filename string) string {
return internal.Base36(repoID) + "_" + filename return internal.Base36(repoID) + "_" + filename
} }
@ -30,3 +34,17 @@ func FilenameOfIndexerID(indexerID string) string {
} }
return indexerID[index+1:] return indexerID[index+1:]
} }
// Given the contents of file, returns the boundaries of its first seven lines.
func FilenameMatchIndexPos(content string) (int, int) {
count := 1
for i, c := range content {
if c == '\n' {
count++
if count == filenameMatchNumberOfLines {
return 0, i
}
}
}
return 0, len(content)
}

View file

@ -11,10 +11,15 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
"github.com/blevesearch/bleve/v2/index/upsidedown" "github.com/blevesearch/bleve/v2/index/upsidedown"
"github.com/ethantkoenig/rupture" "github.com/ethantkoenig/rupture"
) )
const (
maxFuzziness = 2
)
// openIndexer open the index at the specified path, checking for metadata // openIndexer open the index at the specified path, checking for metadata
// updates and bleve version updates. If index needs to be created (or // updates and bleve version updates. If index needs to be created (or
// re-created), returns (nil, nil) // re-created), returns (nil, nil)
@ -48,7 +53,27 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
return index, 0, nil return index, 0, nil
} }
// This method test the GuessFuzzinessByKeyword method. The fuzziness is based on the levenshtein distance and determines how many chars
// may be different on two string and they still be considered equivalent.
// Given a phrasse, its shortest word determines its fuzziness. If a phrase uses CJK (eg: `갃갃갃` `啊啊啊`), the fuzziness is zero.
func GuessFuzzinessByKeyword(s string) int { func GuessFuzzinessByKeyword(s string) int {
tokenizer := unicode.NewUnicodeTokenizer()
tokens := tokenizer.Tokenize([]byte(s))
if len(tokens) > 0 {
fuzziness := maxFuzziness
for _, token := range tokens {
fuzziness = min(fuzziness, guessFuzzinessByKeyword(string(token.Term)))
}
return fuzziness
}
return 0
}
func guessFuzzinessByKeyword(s string) int {
// according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2 // according to https://github.com/blevesearch/bleve/issues/1563, the supported max fuzziness is 2
// magic number 4 was chosen to determine the levenshtein distance per each character of a keyword // magic number 4 was chosen to determine the levenshtein distance per each character of a keyword
// BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot. // BUT, when using CJK (eg: `갃갃갃` `啊啊啊`), it mismatches a lot.
@ -57,5 +82,5 @@ func GuessFuzzinessByKeyword(s string) int {
return 0 return 0
} }
} }
return min(2, len(s)/4) return min(maxFuzziness, len(s)/4)
} }

View file

@ -0,0 +1,45 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package bleve
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBleveGuessFuzzinessByKeyword(t *testing.T) {
scenarios := []struct {
Input string
Fuzziness int // See util.go for the definition of fuzziness in this particular context
}{
{
Input: "",
Fuzziness: 0,
},
{
Input: "Avocado",
Fuzziness: 1,
},
{
Input: "Geschwindigkeit",
Fuzziness: 2,
},
{
Input: "non-exist",
Fuzziness: 0,
},
{
Input: "갃갃갃",
Fuzziness: 0,
},
}
for _, scenario := range scenarios {
t.Run(fmt.Sprintf("ensure fuzziness of '%s' is '%d'", scenario.Input, scenario.Fuzziness), func(t *testing.T) {
assert.Equal(t, scenario.Fuzziness, GuessFuzzinessByKeyword(scenario.Input))
})
}
}

View file

@ -37,6 +37,7 @@ type PullRequest struct {
ForeignIndex int64 ForeignIndex int64
Context DownloaderContext `yaml:"-"` Context DownloaderContext `yaml:"-"`
EnsuredSafe bool `yaml:"ensured_safe"` EnsuredSafe bool `yaml:"ensured_safe"`
IsDraft bool `yaml:"is_draft"`
} }
func (p *PullRequest) GetLocalIndex() int64 { return p.Number } func (p *PullRequest) GetLocalIndex() int64 { return p.Number }

View file

@ -7,11 +7,9 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"strconv"
"time" "time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
) )
@ -24,25 +22,6 @@ const (
GitPushOptionCount = "GIT_PUSH_OPTION_COUNT" GitPushOptionCount = "GIT_PUSH_OPTION_COUNT"
) )
// GitPushOptions is a wrapper around a map[string]string
type GitPushOptions map[string]string
// GitPushOptions keys
const (
GitPushOptionRepoPrivate = "repo.private"
GitPushOptionRepoTemplate = "repo.template"
)
// Bool checks for a key in the map and parses as a boolean
func (g GitPushOptions) Bool(key string) optional.Option[bool] {
if val, ok := g[key]; ok {
if b, err := strconv.ParseBool(val); err == nil {
return optional.Some(b)
}
}
return optional.None[bool]()
}
// HookOptions represents the options for the Hook calls // HookOptions represents the options for the Hook calls
type HookOptions struct { type HookOptions struct {
OldCommitIDs []string OldCommitIDs []string

View file

@ -0,0 +1,45 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package private
import (
"strconv"
"strings"
"code.gitea.io/gitea/modules/optional"
)
// GitPushOptions is a wrapper around a map[string]string
type GitPushOptions map[string]string
// GitPushOptions keys
const (
GitPushOptionRepoPrivate = "repo.private"
GitPushOptionRepoTemplate = "repo.template"
GitPushOptionForcePush = "force-push"
)
// Bool checks for a key in the map and parses as a boolean
// An option without value is considered true, eg: "-o force-push" or "-o repo.private"
func (g GitPushOptions) Bool(key string) optional.Option[bool] {
if val, ok := g[key]; ok {
if val == "" {
return optional.Some(true)
}
if b, err := strconv.ParseBool(val); err == nil {
return optional.Some(b)
}
}
return optional.None[bool]()
}
// AddFromKeyValue adds a key value pair to the map by "key=value" format or "key" for empty value
func (g GitPushOptions) AddFromKeyValue(line string) {
kv := strings.SplitN(line, "=", 2)
if len(kv) == 2 {
g[kv[0]] = kv[1]
} else {
g[kv[0]] = ""
}
}

View file

@ -0,0 +1,30 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package private
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGitPushOptions(t *testing.T) {
o := GitPushOptions{}
v := o.Bool("no-such")
assert.False(t, v.Has())
assert.False(t, v.Value())
o.AddFromKeyValue("opt1=a=b")
o.AddFromKeyValue("opt2=false")
o.AddFromKeyValue("opt3=true")
o.AddFromKeyValue("opt4")
assert.Equal(t, "a=b", o["opt1"])
assert.False(t, o.Bool("opt1").Value())
assert.True(t, o.Bool("opt2").Has())
assert.False(t, o.Bool("opt2").Value())
assert.True(t, o.Bool("opt3").Value())
assert.True(t, o.Bool("opt4").Value())
}

View file

@ -219,6 +219,7 @@ const (
type IssueCommentPayload struct { type IssueCommentPayload struct {
Action HookIssueCommentAction `json:"action"` Action HookIssueCommentAction `json:"action"`
Issue *Issue `json:"issue"` Issue *Issue `json:"issue"`
PullRequest *PullRequest `json:"pull_request,omitempty"`
Comment *Comment `json:"comment"` Comment *Comment `json:"comment"`
Changes *ChangesPayload `json:"changes,omitempty"` Changes *ChangesPayload `json:"changes,omitempty"`
Repository *Repository `json:"repository"` Repository *Repository `json:"repository"`

View file

@ -580,6 +580,8 @@ lang_select_error=Sélectionnez une langue dans la liste.
username_been_taken=Le nom d'utilisateur est déjà pris. username_been_taken=Le nom d'utilisateur est déjà pris.
username_change_not_local_user=Les utilisateurs non-locaux n'ont pas le droit de modifier leur nom d'utilisateur. username_change_not_local_user=Les utilisateurs non-locaux n'ont pas le droit de modifier leur nom d'utilisateur.
change_username_disabled=Le changement de nom dutilisateur est désactivé.
change_full_name_disabled=Le changement de nom complet est désactivé.
username_has_not_been_changed=Le nom d'utilisateur n'a pas été modifié username_has_not_been_changed=Le nom d'utilisateur n'a pas été modifié
repo_name_been_taken=Ce nom de dépôt est déjà utilisé. repo_name_been_taken=Ce nom de dépôt est déjà utilisé.
repository_force_private=Force Private est activé : les dépôts privés ne peuvent pas être rendus publics. repository_force_private=Force Private est activé : les dépôts privés ne peuvent pas être rendus publics.
@ -1039,6 +1041,7 @@ issue_labels_helper=Sélectionner un jeu de label.
license=Licence license=Licence
license_helper=Sélectionner une licence license_helper=Sélectionner une licence
license_helper_desc=Une licence réglemente ce que les autres peuvent ou ne peuvent pas faire avec votre code. Vous ne savez pas laquelle est la bonne pour votre projet ? Comment <a target="_blank" rel="noopener noreferrer" href="%s">choisir une licence</a>. license_helper_desc=Une licence réglemente ce que les autres peuvent ou ne peuvent pas faire avec votre code. Vous ne savez pas laquelle est la bonne pour votre projet ? Comment <a target="_blank" rel="noopener noreferrer" href="%s">choisir une licence</a>.
multiple_licenses=Licences multiples
object_format=Format d'objet object_format=Format d'objet
object_format_helper=Format dobjet pour ce dépôt. Ne peut être modifié plus tard. SHA1 est le plus compatible. object_format_helper=Format dobjet pour ce dépôt. Ne peut être modifié plus tard. SHA1 est le plus compatible.
readme=LISEZMOI readme=LISEZMOI
@ -1834,7 +1837,7 @@ pulls.is_empty=Les changements sur cette branche sont déjà sur la branche cibl
pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi. pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi.
pulls.required_status_check_missing=Certains contrôles requis sont manquants. pulls.required_status_check_missing=Certains contrôles requis sont manquants.
pulls.required_status_check_administrator=En tant qu'administrateur, vous pouvez toujours fusionner cette requête de pull. pulls.required_status_check_administrator=En tant qu'administrateur, vous pouvez toujours fusionner cette requête de pull.
pulls.blocked_by_approvals=Cette demande d'ajout nest pas suffisamment approuvée. %d approbations obtenues sur %d. pulls.blocked_by_approvals=Cette demande dajout nest pas suffisamment approuvée. %d approbations obtenues sur %d.
pulls.blocked_by_approvals_whitelisted=Cette demande dajout na pas encore assez dapprobations. %d sur %d approbations de la part des utilisateurs ou équipes sur la liste autorisée. pulls.blocked_by_approvals_whitelisted=Cette demande dajout na pas encore assez dapprobations. %d sur %d approbations de la part des utilisateurs ou équipes sur la liste autorisée.
pulls.blocked_by_rejection=Cette demande dajout nécessite des corrections sollicitées par un évaluateur officiel. pulls.blocked_by_rejection=Cette demande dajout nécessite des corrections sollicitées par un évaluateur officiel.
pulls.blocked_by_official_review_requests=Cette demande dajout a des sollicitations officielles dévaluation. pulls.blocked_by_official_review_requests=Cette demande dajout a des sollicitations officielles dévaluation.
@ -2940,6 +2943,7 @@ dashboard.start_schedule_tasks=Démarrer les tâches planifiées
dashboard.sync_branch.started=Début de la synchronisation des branches dashboard.sync_branch.started=Début de la synchronisation des branches
dashboard.sync_tag.started=Synchronisation des étiquettes dashboard.sync_tag.started=Synchronisation des étiquettes
dashboard.rebuild_issue_indexer=Reconstruire lindexeur des tickets dashboard.rebuild_issue_indexer=Reconstruire lindexeur des tickets
dashboard.sync_repo_licenses=Synchroniser les licences du dépôt
users.user_manage_panel=Gestion du compte utilisateur users.user_manage_panel=Gestion du compte utilisateur
users.new_account=Créer un compte users.new_account=Créer un compte

View file

@ -2890,17 +2890,167 @@ dashboard.delete_generated_repository_avatars=Scrios abhatáranna stórtha ginte
dashboard.sync_repo_branches=Sync brainsí caillte ó shonraí git go bunachair sonraí dashboard.sync_repo_branches=Sync brainsí caillte ó shonraí git go bunachair sonraí
dashboard.sync_repo_tags=Clibeanna sioncraigh ó shonraí git go bunachar sonraí dashboard.sync_repo_tags=Clibeanna sioncraigh ó shonraí git go bunachar sonraí
dashboard.update_mirrors=Scátháin a nuashonrú dashboard.update_mirrors=Scátháin a nuashonrú
dashboard.repo_health_check=Seiceáil sláinte gach stóras
dashboard.check_repo_stats=Seiceáil gach staitisticí stórais
dashboard.archive_cleanup=Scrios sean-chartlanna stórais
dashboard.deleted_branches_cleanup=Brainsí scriosta a ghlanadh
dashboard.update_migration_poster_id=Nuashonraigh ID póstaer imir
dashboard.git_gc_repos=Bailíonn truflais gach stórais
dashboard.resync_all_sshkeys=Nuashonraigh an comhad '.ssh/authorized_keys' le heochracha Gitea SSH.
dashboard.resync_all_sshprincipals=Nuashonraigh an comhad '.ssh/authorized_principals' le príomhphrionsabail Gitea SSH.
dashboard.resync_all_hooks=Athshioncrónaigh crúcaí réamhfhála, nuashonraithe agus iar-fhála na stórtha go léir.
dashboard.reinit_missing_repos=Aththosaigh gach stórais Git atá in easnamh a bhfuil taifid ann dóibh
dashboard.sync_external_users=Sioncrónaigh sonraí úsáideoirí seachtracha
dashboard.cleanup_hook_task_table=Tábla hook_task glantacháin
dashboard.cleanup_packages=Pacáistí glanta in éag
dashboard.cleanup_actions=Gníomhaíochtaí glanta in éag acmhainní
dashboard.server_uptime=Aga fónaimh Freastalaí
dashboard.current_goroutine=Goroutines Reatha
dashboard.current_memory_usage=Úsáid Cuimhne Reatha
dashboard.total_memory_allocated=Cuimhne Iomlán Leithdháilte
dashboard.memory_obtained=Cuimhne Faighte
dashboard.pointer_lookup_times=Amanna Cuardaigh Pointeora
dashboard.memory_allocate_times=Leithdháiltí Cuimhne
dashboard.memory_free_times=Saorálann Cuimhne
dashboard.current_heap_usage=Úsáid Charn Reatha
dashboard.heap_memory_obtained=Cuimhne Charn Faighte
dashboard.heap_memory_idle=Díomhaoin Cuimhne Carn
dashboard.heap_memory_in_use=Cuimhne Carm In Úsáid
dashboard.heap_memory_released=Cuimhne Carn Eisithe
dashboard.heap_objects=Cuspóirí Carn
dashboard.bootstrap_stack_usage=Úsáid Staca Bootstrap
dashboard.stack_memory_obtained=Cuimhne Staca Faighte
dashboard.mspan_structures_usage=Úsáid Struchtúir MSpan
dashboard.mspan_structures_obtained=Struchtúir MSpan a Faightear
dashboard.mcache_structures_usage=Úsáid Struchtúir MCache
dashboard.mcache_structures_obtained=Struchtúir MCache a Faightear
dashboard.profiling_bucket_hash_table_obtained=Tábla Hash Buicéad Próifílithe a Faightear
dashboard.gc_metadata_obtained=Meiteashonraí GC faighte
dashboard.other_system_allocation_obtained=Leithdháileadh Córais Eile a Fuarthas
dashboard.next_gc_recycle=Athchúrsáil GC Eile
dashboard.last_gc_time=Ó Am Deiridh GC
dashboard.total_gc_time=Sos Iomlán GC
dashboard.total_gc_pause=Sos Iomlán GC
dashboard.last_gc_pause=Sos GC Deireanach
dashboard.gc_times=Amanna GC
dashboard.delete_old_actions=Scrios gach sean-ghníomhaíocht ón mbunachar
dashboard.delete_old_actions.started=Scrios na sean-ghníomhaíocht go léir ón mbunachar sonraí tosaithe.
dashboard.update_checker=Seiceoir nuashonraithe
dashboard.delete_old_system_notices=Scrios gach seanfhógra córais ón mbunachar sonraí
dashboard.gc_lfs=Bailigh truflais meta rudaí LFS
dashboard.stop_zombie_tasks=Stad gníomhartha tascanna zombie
dashboard.stop_endless_tasks=Stad gníomhartha tascanna gan deireadh
dashboard.cancel_abandoned_jobs=Cealaigh gníomhartha poist tréigthe
dashboard.start_schedule_tasks=Tosaigh tascanna sceideal gníom
dashboard.sync_branch.started=Thosaigh Brainsí Sioncronú
dashboard.sync_tag.started=Clibeanna Thosaigh Sioncronú
dashboard.rebuild_issue_indexer=Atógáil innéacsóir eisiúna
dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo dashboard.sync_repo_licenses=Sioncronaigh ceadúnais repo
users.user_manage_panel=Bainistíocht Cuntas Úsáideora
users.new_account=Cruthaigh cuntas Úsáideora
users.name=Ainm úsáideora
users.full_name=Ainm Iomlán users.full_name=Ainm Iomlán
users.activated=Gníomhachtaithe
users.admin=Riarachán
users.restricted=Srianta
users.reserved=In áirithe
users.bot=Bota
users.remote=Iargúlta
users.2fa=2FA
users.repos=Stórais
users.created=Cruthaithe
users.last_login=Sínigh Isteach Deiridh
users.never_login=Ná Sínigh Isteach riamh
users.send_register_notify=Seol Fógra um Chlárú Úsáideora
users.new_success=Tá an cuntas úsáideora "%s" cruthaithe.
users.edit=Eagar
users.auth_source=Foinse Fíordheimhnithe
users.local=Áitiúil
users.auth_login_name=Ainm Síniú Isteach Fíordheimhnithe
users.password_helper=Fág an pasfhocal folamh chun é a choinneáil gan athrú.
users.update_profile_success=Nuashonraíodh an cuntas úsáideora.
users.edit_account=Cuir Cuntas Úsáideora in Eagar
users.max_repo_creation=Uasmhéid Stóras
users.max_repo_creation_desc=(Cuir isteach -1 chun an teorainn réamhshocraithe domhanda a úsáid.)
users.is_activated=Gníomhachtaítear Cuntas Úsáideora
users.prohibit_login=Díchumasaigh Síniú Isteach
users.is_admin=Is Riarthóir
users.is_restricted=Is Srianta
users.allow_git_hook=Féadfaidh Git Hooks a Chruthú
users.allow_git_hook_tooltip=Déantar Git Hooks a fhorghníomhú mar úsáideoir OS a ritheann Gitea agus beidh an leibhéal céanna rochtana óstaigh aige. Mar thoradh air sin, is féidir le húsáideoirí a bhfuil an phribhléid speisialta Git Hook seo acu rochtain a fháil ar gach stór Gitea agus iad a mhodhnú chomh maith leis an mbunachar sonraí a úsáideann Gitea. Dá bharr sin tá siad in ann pribhléidí riarthóra Gitea a fháil freisin.
users.allow_import_local=Is féidir Stórais Áitiúla a Allmhairiú
users.allow_create_organization=Is féidir Eagraíochtaí a Chruthú
users.update_profile=Nuashonraigh Cuntas Úsáideora
users.delete_account=Scrios Cuntas Úsáide
users.cannot_delete_self=Ní féidir leat tú féin a scriosadh
users.still_own_repo=Tá stórais amháin nó níos mó fós ag an úsáideoir seo. Scrios nó aistrigh na stórais seo ar dtús.
users.still_has_org=Is ball d'eagraíocht é an t-úsáideoir seo. Bain an t-úsáideoir ó aon eagraíochtaí ar dtús.
users.purge=Úsáideoir a Ghlanadh
users.purge_help=Scrios go héigeantach úsáideoir agus aon stórais, eagraíochtaí agus pacáistí atá faoi úinéireacht an úsáideora. Scriosfar gach trácht freisin.
users.still_own_packages=Tá pacáiste amháin nó níos mó fós ag an úsáideoir seo, scrios na pacáistí seo ar dtús.
users.deletion_success=Scriosadh an cuntas úsáideora.
users.reset_2fa=Athshocraigh 2FA
users.list_status_filter.menu_text=Scagaire
users.list_status_filter.reset=Athshocraigh
users.list_status_filter.is_active=Gníomhach users.list_status_filter.is_active=Gníomhach
users.list_status_filter.not_active=Neamhghníomhach
users.list_status_filter.is_admin=Riarachán
users.list_status_filter.not_admin=Ní Riarachán
users.list_status_filter.is_restricted=Srianta
users.list_status_filter.not_restricted=Gan Srian
users.list_status_filter.is_prohibit_login=Cosc ar Logáil Isteach
users.list_status_filter.not_prohibit_login=Ceadaigh Logáil isteach
users.list_status_filter.is_2fa_enabled=2FA Cumasaithe
users.list_status_filter.not_2fa_enabled=2FA faoi mhíchumas
users.details=Sonraí Úsáideora
emails.email_manage_panel=Bainistíocht Ríomhphost Úsáideoir
emails.primary=Bunscoile
emails.activated=Gníomhachtaithe
emails.filter_sort.email=Ríomhphost
emails.filter_sort.email_reverse=Ríomhphost (droim ar ais)
emails.filter_sort.name=Ainm Úsáideora
emails.filter_sort.name_reverse=Ainm Úsáideora (droim ar ais)
emails.updated=Nuashonraíodh an ríomhphost
emails.not_updated=Theip ar an seoladh ríomhphoist iarrtha a nuashonrú: %v
emails.duplicate_active=Tá an seoladh ríomhphoist seo gníomhach cheana féin d'úsáideoir difriúil.
emails.change_email_header=Nuashonraigh Airíonna Ríomhphoist
emails.change_email_text=An bhfuil tú cinnte gur mhaith leat an seoladh ríomhphoist seo a nuashonrú?
emails.delete=Scrios Ríomhphost
emails.delete_desc=An bhfuil tú cinnte gur mhaith leat an seoladh ríomhphoist seo a scriosadh?
emails.deletion_success=Tá an seoladh ríomhphoist scriosta.
emails.delete_primary_email_error=Ní féidir leat an ríomhphost príomhúil a scriosadh.
orgs.org_manage_panel=Bainistíocht Eagraíochta
orgs.name=Ainm
orgs.teams=Foirne orgs.teams=Foirne
orgs.members=Comhaltaí
orgs.new_orga=Eagraíocht Nua
repos.repo_manage_panel=Bainistíocht Stórais
repos.unadopted=Stórais Neamhghlactha
repos.unadopted.no_more=Níor aimsíodh níos mó stórais neamhghlactha
repos.owner=Úinéir repos.owner=Úinéir
repos.name=Ainm
repos.private=Príobháideach
repos.issues=Saincheisteanna
repos.size=Méid
repos.lfs_size=Méid LFS
packages.package_manage_panel=Bainistíocht Pacáiste
packages.total_size=Méid Iomlán: %s
packages.unreferenced_size=Méid gan tagairt: %s
packages.cleanup=Glan suas sonraí in éag
packages.cleanup.success=Glanadh suas sonraí in éag go rathúil
packages.owner=Úinéir packages.owner=Úinéir
packages.creator=Cruthaitheoir
packages.name=Ainm
packages.version=Leagan
packages.type=Cineál
packages.repository=Stóráil
packages.size=Méid
packages.published=Foilsithe
defaulthooks=Réamhshocraithe Crúcaí Gréasán defaulthooks=Réamhshocraithe Crúcaí Gréasán
defaulthooks.desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibríoch chuig freastalaí nuair a chuireann imeachtaí áirithe Gitea tús. Is mainneachtainí iad na cuacha gréasáin a shainítear anseo agus déanfar iad a chóipeáil isteach i ngach stórais nua. Léigh tuilleadh sa <a target="_blank" rel="noopener" href="%s">treoir chúca Crúcaí Gréasán</a>. defaulthooks.desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibríoch chuig freastalaí nuair a chuireann imeachtaí áirithe Gitea tús. Is mainneachtainí iad na cuacha gréasáin a shainítear anseo agus déanfar iad a chóipeáil isteach i ngach stórais nua. Léigh tuilleadh sa <a target="_blank" rel="noopener" href="%s">treoir chúca Crúcaí Gréasán</a>.
@ -2912,60 +3062,636 @@ systemhooks.desc=Déanann Crúcaí Gréasán iarratais HTTP POST go huathoibrío
systemhooks.add_webhook=Cuir Crúca Gréasán Córas leis systemhooks.add_webhook=Cuir Crúca Gréasán Córas leis
systemhooks.update_webhook=Nuashonraigh Córas Crúca Gréasán systemhooks.update_webhook=Nuashonraigh Córas Crúca Gréasán
auths.auth_manage_panel=Bainistiú Foinse Fíordheimhnithe
auths.new=Cuir Foinse Fíordheimhni
auths.name=Ainm
auths.type=Cineál
auths.enabled=Cumasaithe
auths.syncenabled=Cumasaigh Sioncrónú Úsáideora
auths.updated=Nuashonraithe auths.updated=Nuashonraithe
auths.auth_type=Cineál Fíordheimhnithe
auths.auth_name=Ainm Fíordheimhnithe
auths.security_protocol=Prótacal Slándála
auths.domain=Fearann auths.domain=Fearann
auths.host=Óstach
auths.port=Calafort
auths.bind_dn=Ceangail DN
auths.bind_password=Ceangail Pasfhocal
auths.user_base=Bonn Cuardaigh Úsáideora
auths.user_dn=Úsáideoir DN
auths.attribute_username=Tréith Ainm Úsáideora
auths.attribute_username_placeholder=Fág folamh chun an t-ainm úsáideora a iontráiltear i Gitea a úsáid.
auths.attribute_name=Tréith Céad Ainm
auths.attribute_surname=Tréith Sloinne
auths.attribute_mail=Tréith ríomhphoist
auths.attribute_ssh_public_key=Tréith Eochair SSH Phoiblí
auths.attribute_avatar=Tréith Avatar
auths.attributes_in_bind=Faigh tréithe i gComhthéacs Bind DN
auths.allow_deactivate_all=Lig do thoradh cuardaigh folamh gach úsáideoir a dhíghníomhachtú
auths.use_paged_search=Úsáid Cuardach Leathanaigh
auths.search_page_size=Méid an Leathanaigh
auths.filter=Scagaire Úsáideora
auths.admin_filter=Scagaire Riaracháin
auths.restricted_filter=Scagaire Srianta
auths.restricted_filter_helper=Fág folamh chun aon úsáideoirí a shocrú mar theoranta. Úsáid réiltín ('*') chun gach úsáideoir nach meaitseálann Scagaire Riaracháin a shocrú mar theoranta.
auths.verify_group_membership=Fíoraigh ballraíocht ghrúpa i LDAP (fág an scagaire folamh le scipeáil)
auths.group_search_base=Bonn Cuardaigh Grúpa DN
auths.group_attribute_list_users=Tréith Grúpa ina bhfuil Liosta Úsáideoirí
auths.user_attribute_in_group=Tréith Úsáideora atá Liostaithe i nGrúpa
auths.map_group_to_team=Léarscáil grúpaí LDAP chuig foirne na hEagraíochta (fág an réimse folamh le scipeáil)
auths.map_group_to_team_removal=Bain úsáideoirí ó fhoirne sioncronaithe mura mbaineann an t-úsáideoir leis an ngrúpa comhfhreagrach LDAP
auths.enable_ldap_groups=Cumasaigh grúpaí LDAP
auths.ms_ad_sa=MS AD Tréithe Cuardaigh
auths.smtp_auth=Cineál Fíordheimhnithe SMTP
auths.smtphost=Óstach SMTP
auths.smtpport=SMTP Calafort
auths.allowed_domains=Fearainn Ceadaithe
auths.allowed_domains_helper=Fág folamh chun gach fearann a cheadú. Déan ilfhearann a scaradh le camóg (',').
auths.skip_tls_verify=Scipeáil Fíorú TLS
auths.force_smtps=Fórsa SMTPS
auths.force_smtps_helper=Úsáidtear SMTPS i gcónaí ar chalafort 465. Socraigh é seo chun SMTPS a chur i bhfeidhm ar chalafoirt eile. (Seachas sin úsáidfear STARTTLS ar chalafoirt eile má thacaíonn an t-óstach leis.)
auths.helo_hostname=Ainm Óstach HELO
auths.helo_hostname_helper=Ainm óstach a sheoltar le HELO. Fág bán chun an t-ainm óstach reatha a sheoladh.
auths.disable_helo=Díchumasaigh HELO
auths.pam_service_name=Ainm Seirbhíse PAM
auths.pam_email_domain=Fearann Ríomhphoist PAM (roghnach)
auths.oauth2_provider=Soláthraí OAuth2
auths.oauth2_icon_url=URL deilbhín
auths.oauth2_clientID=Aitheantas Cliant (Eochair)
auths.oauth2_clientSecret=Rúnda Cliant
auths.openIdConnectAutoDiscoveryURL=URL Fionnachtana Uathoibríoch OpenID Connect
auths.oauth2_use_custom_url=Úsáid URLanna Saincheaptha in ionad URLanna Réamhshocraithe
auths.oauth2_tokenURL=URL Comhartha
auths.oauth2_authURL=Údaraigh URL
auths.oauth2_profileURL=URL Próifíl
auths.oauth2_emailURL=URL ríomhphoist
auths.skip_local_two_fa=Scipeáil 2FA áitiúil
auths.skip_local_two_fa_helper=Ciallaíonn fágáil gan socrú go mbeidh ar úsáideoirí áitiúla a bhfuil tacar 2FA acu 2FA a rith fós chun logáil isteach
auths.oauth2_tenant=Tionónta
auths.oauth2_scopes=Scóipeanna Breise
auths.oauth2_required_claim_name=Ainm Éilimh Riachtanach
auths.oauth2_required_claim_name_helper=Socraigh an t-ainm seo chun logáil isteach ón bhfoinse seo a shrianadh d'úsáideoirí a bhfuil éileamh acu leis an ainm seo
auths.oauth2_required_claim_value=Luach Éilimh Riachtanach
auths.oauth2_required_claim_value_helper=Socraigh an luach seo chun logáil isteach ón bhfoinse seo a shrianadh chuig úsáideoirí a bhfuil éileamh acu leis an ainm agus an luach seo
auths.oauth2_group_claim_name=Ainm éileamh ag soláthar ainmneacha grúpa don fhoinse seo (Roghnach)
auths.oauth2_admin_group=Luach Éilimh Grúpa d'úsáideoirí riarthóra. (Roghnach - teastaíonn ainm éilimh thuas)
auths.oauth2_restricted_group=Luach Éilimh Grúpa d'úsáideoirí srianta. (Roghnach - teastaíonn ainm éilimh thuas)
auths.oauth2_map_group_to_team=Map mhaígh grúpaí chuig foirne Eagraíochta. (Roghnach - éilíonn ainm an éilimh thuas)
auths.oauth2_map_group_to_team_removal=Bain úsáideoirí ó fhoirne sioncronaithe mura mbaineann an t-úsáideoir leis an ngrúpa comhfhreagrach.
auths.enable_auto_register=Cumasaigh Clárú Auto
auths.sspi_auto_create_users=Cruthaigh úsáideoirí go huathoibríoch
auths.sspi_auto_create_users_helper=Lig do mhodh auth SSPI cuntais nua a chruthú go huathoibríoch d'úsáideoirí a logálann isteach den chéad uair
auths.sspi_auto_activate_users=Gníomhachtaigh úsáideoirí go huathoibríoch
auths.sspi_auto_activate_users_helper=Lig modh auth SSPI úsáideoirí nua a ghníomhachtú go huathoibríoch
auths.sspi_strip_domain_names=Bain ainmneacha fearann ó ainm úsáideora
auths.sspi_strip_domain_names_helper=Má dhéantar iad a sheiceáil, bainfear ainmneacha fearainn ó ainmneacha logála isteach (m.sh. Beidh “DOMAIN\ user” agus "user@example.org" araon ní bheidh ach “úsáideoir”).
auths.sspi_separator_replacement=Deighilteoir le húsáid in ionad\,/agus @
auths.sspi_separator_replacement_helper=An carachtar a úsáidfear chun na deighilteoirí a chur in ionad na n-ainmneacha logála síos-leibhéil (m.sh. an \ i "DOMAIN\úsáideoir") agus ainmneacha príomhoidí úsáideora (m.sh. an @ in "user@example.org").
auths.sspi_default_language=Teanga úsáideora réamhshocraithe
auths.sspi_default_language_helper=Teanga réamhshocraithe d'úsáideoirí cruthaithe go huathoibríoch ag modh auth SSPI. Fág folamh más fearr leat teanga a bhrath go huathoibríoch.
auths.tips=Leideanna
auths.tips.oauth2.general=OAuth2 Fíordheimhniú
auths.tips.oauth2.general.tip=Agus fíordheimhniú OAuth2 nua á chlárú agat, ba chóir go mbeadh an URL glaonna ais/atreoraithe:
auths.tip.oauth2_provider=Soláthraí OAuth2
auths.tip.bitbucket=Cláraigh tomhaltóir OAuth nua ar %s agus cuir an cead 'Cuntas' - 'Léigh' leis
auths.tip.nextcloud=`Cláraigh tomhaltóir OAuth nua ar do chás ag baint úsáide as an roghchlár seo a leanas "Socruithe -> Slándáil -> cliant OAuth 2.0"`
auths.tip.dropbox=Cruthaigh feidhmchlár nua ag %s
auths.tip.facebook=Cláraigh feidhmchlár nua ag %s agus cuir an táirge "Facebook Login" leis
auths.tip.github=Cláraigh feidhmchlár OAuth nua ar %s
auths.tip.gitlab_new=Cláraigh feidhmchlár nua ar %s
auths.tip.google_plus=Faigh dintiúir chliaint OAuth2 ó chonsól API Google ag %s
auths.tip.openid_connect=Úsáid URL Fionnachtana OpenID Connect "https://{server}/.well-known/openid-configuration" chun na críochphointí a shonrú
auths.tip.twitter=Téigh go %s, cruthaigh feidhmchlár agus cinntigh go bhfuil an rogha "Ceadaigh úsáid a bhaint as an bhfeidhmchlár seo chun logáil isteach le Twitter" cumasaithe
auths.tip.discord=Cláraigh feidhmchlár nua ar %s
auths.tip.gitea=Cláraigh feidhmchlár OAuth2 nua. Tá treoir le fáil ag %s
auths.tip.yandex=`Cruthaigh feidhmchlár nua ag %s. Roghnaigh na ceadanna seo a leanas ón rannán "Yandex.Passport API": "Rochtain ar sheoladh ríomhphoist", "Rochtain ar avatar úsáideora" agus "Rochtain ar ainm úsáideora, céad ainm agus sloinne, inscne"`
auths.tip.mastodon=Ionchur URL sampla saincheaptha don shampla mastodon is mian leat a fhíordheimhniú leis (nó bain úsáid as an gceann réamhshocraithe)
auths.edit=Cuir Foinse Fíordheimhnithe in Eagar
auths.activated=Tá an Foinse Fíordheimhnithe seo gníomhachtaithe
auths.new_success=Tá an fíordheimhniú "%s" curtha leis.
auths.update_success=Nuashonraíodh an fhoinse fíordheimhnithe.
auths.update=Nuashonraigh Foinse Fíordheimhnithe
auths.delete=Scrios Foinse Fíordheimhnithe
auths.delete_auth_title=Scrios Foinse Fíordheimhnithe
auths.delete_auth_desc=Má scriosann tú foinse fíordheimhnithe cuirtear cosc ar úsáideoirí í a úsáid chun síniú isteach. Lean ort?
auths.still_in_used=Tá an fhoinse fíordheimhnithe fós in úsáid. Tiontaigh nó scrios aon úsáideoir a úsáideann an fhoinse fíordheimhnithe seo ar dtús.
auths.deletion_success=Tá an fhoinse fíordheimhnithe scriosta.
auths.login_source_exist=Tá an fhoinse fíordheimhnithe "%s" ann cheana.
auths.login_source_of_type_exist=Tá foinse fíordheimhnithe den chineál seo ann cheana féin.
auths.unable_to_initialize_openid=Ní féidir Soláthraí Ceangail OpenID a thionscnamh: %s
auths.invalid_openIdConnectAutoDiscoveryURL=URL Neamhbhailí Fionnachtana Uathoibríoch (ní mór gur URL bailí é seo ag tosú le http:// nó https://)
config.server_config=Cumraíocht Freastalaí
config.app_name=Teideal an Láithreáin
config.app_ver=Leagan Gitea
config.app_url=URL Bonn Gitea
config.custom_conf=Cosán Comhad Cumraíochta
config.custom_file_root_path=Cosán Fréamh Comhad Saincheaptha
config.domain=Fearann Freastalaí
config.offline_mode=Mód Áitiúil
config.disable_router_log=Díchumasaigh Loga an Ródaire
config.run_user=Rith Mar Ainm úsáideora
config.run_mode=Mód Rith
config.git_version=Leagan Git
config.app_data_path=Cosán Sonraí Aip
config.repo_root_path=Cosán Fréimhe Stórála
config.lfs_root_path=Cosán Fréamh LFS
config.log_file_root_path=Cosán Logála
config.script_type=Cineál Script
config.reverse_auth_user=Úsáideoir Fíordheimhnithe Droim ar Ais
config.ssh_config=Cumraíocht SSH
config.ssh_enabled=Cumasaithe
config.ssh_start_builtin_server=Úsáid Freastalaí Ionsuite
config.ssh_domain=Fearainn Freastalaí SSH
config.ssh_port=Calafort
config.ssh_listen_port=Éist Calafort
config.ssh_root_path=Cosán Fréimhe
config.ssh_key_test_path=Cosán Tástáil Eochair
config.ssh_keygen_path=Keygen ('ssh-keygen') Cosán
config.ssh_minimum_key_size_check=Seiceáil Íosta Méid Eochair
config.ssh_minimum_key_sizes=Méideanna Íosta Eochrach
config.lfs_config=Cumraíocht LFS
config.lfs_enabled=Cumasaithe
config.lfs_content_path=Cosán Ábhar LFS
config.lfs_http_auth_expiry=Éag Auth LFS HTTP
config.db_config=Cumraíocht Bunachar Sonraí
config.db_type=Cineál
config.db_host=Óstach
config.db_name=Ainm
config.db_user=Ainm úsáideora
config.db_schema=Scéim
config.db_ssl_mode=SSL
config.db_path=Cosán
config.service_config=Cumraíocht Seirbhíse
config.register_email_confirm=Deimhniú Ríomhphost a éileamh chun Clárú
config.disable_register=Díchumasaigh Féin-Chlárú
config.allow_only_internal_registration=Ceadaigh Clárú Amháin Trí Gitea féin
config.allow_only_external_registration=Ceadaigh Clárú Trí Sheirbhísí Seachtracha amháin
config.enable_openid_signup=Cumasaigh Féinchlárú OpenID
config.enable_openid_signin=Cumasaigh Síniú isteach OpenID
config.show_registration_button=Taispeáin Cnaipe Cláraithe
config.require_sign_in_view=Teastaíonn Sínigh isteach chun Leathanaigh Amharc
config.mail_notify=Cumasaigh Fógraí Ríomhphoist
config.enable_captcha=Cumasaigh CAPTCHA
config.active_code_lives=Saol Gníomhach ag an gCód
config.reset_password_code_lives=Am Éaga Chóid Aisghabhála Cuntais
config.default_keep_email_private=Folaigh Seoltaí Ríomhphoist de réir Réamhshocrú
config.default_allow_create_organization=Ceadaigh Cruthú Eagraíochtaí de réir Réamhshocrú
config.enable_timetracking=Cumasaigh Rianú Ama
config.default_enable_timetracking=Cumasaigh Rianú Ama de réir Réamhshocrú
config.default_allow_only_contributors_to_track_time=Lig do Rannpháirtithe Amháin Rianú Am
config.no_reply_address=Fearann Ríomhphoist Folaithe
config.default_visibility_organization=Infheictheacht réamhshocraithe d'Eagraíochtaí nua
config.default_enable_dependencies=Cumasaigh Spleáchais Eisithe de réir Réamhshocrú
config.webhook_config=Cumraíocht Crúca Gréasán config.webhook_config=Cumraíocht Crúca Gréasán
config.queue_length=Fad scuaine
config.deliver_timeout=Teorainn Ama Seachadta
config.skip_tls_verify=Scipeáil Fíorú TLS
config.mailer_config=Cumraíocht Seoltóra
config.mailer_enabled=Cumasaithe
config.mailer_enable_helo=Cumasaigh HELO
config.mailer_name=Ainm
config.mailer_protocol=Prótacal
config.mailer_smtp_addr=Seoladh SMTP
config.mailer_smtp_port=Calafort SMTP
config.mailer_user=Úsáideoir
config.mailer_use_sendmail=Úsáid Sendmail
config.mailer_sendmail_path=Cosán Sendmail
config.mailer_sendmail_args=Argóintí Breise chuig Sendmail
config.mailer_sendmail_timeout=Teorainn Ama Sendmail
config.mailer_use_dummy=Caochadán
config.test_email_placeholder=Ríomhphost (m.sh. test@example.com)
config.send_test_mail=Seol Ríomhphost Tástála
config.send_test_mail_submit=Seol
config.test_mail_failed=Theip ar ríomhphost tástála a sheoladh chuig "%s": %v
config.test_mail_sent=Tá ríomhphost tástála seolta chuig "%s".
config.oauth_config=Cumraíocht OAuth
config.oauth_enabled=Cumasaithe
config.cache_config=Cumraíocht taisce
config.cache_adapter=Cuibheoir taisce
config.cache_interval=Eatramh Taisce
config.cache_conn=Ceangal Taisce
config.cache_item_ttl=Mír Taisce TTL
config.cache_test=Taisce Tástáil
config.cache_test_failed=Theip ar an taisce a thaiscéaladh: %v.
config.cache_test_slow=D'éirigh leis an tástáil taisce, ach tá an freagra mall: %s.
config.cache_test_succeeded=D'éirigh leis an tástáil taisce, fuair sé freagra i %s.
config.session_config=Cumraíocht Seisiúin
config.session_provider=Soláthraí Seisiúin
config.provider_config=Cumraíocht Soláthraí
config.cookie_name=Ainm Fianán
config.gc_interval_time=Am Eatramh GC
config.session_life_time=Am Saoil na Seisiúin
config.https_only=HTTPS Amháin
config.cookie_life_time=Am Saoil Fianán
config.picture_config=Cumraíocht Pictiúr agus Avatar
config.picture_service=Seirbhís Pictiúr
config.disable_gravatar=Díchumasaigh Gravatar
config.enable_federated_avatar=Cumasaigh Avatars Cónaidhme
config.open_with_editor_app_help=Na heagarthóirí "Oscailte le" don roghchlár Clón. Má fhágtar folamh é, úsáidfear an réamhshocrú. Leathnaigh chun an réamhshocrú a fheiceáil.
config.git_config=Cumraíocht Git
config.git_disable_diff_highlight=Díchumasaigh Aibhsiú Comhréire Diff
config.git_max_diff_lines=Max Diff Lines (do chomhad amháin)
config.git_max_diff_line_characters=Carachtair Max Diff (le haghaidh líne amháin)
config.git_max_diff_files=Comhaid Max Diff (le taispeáint)
config.git_gc_args=Argóintí GC
config.git_migrate_timeout=Teorainn Ama Imirce
config.git_mirror_timeout=Teorainn Ama Nuashonraithe Scátháin
config.git_clone_timeout=Teorainn Ama Oibríochta Clón
config.git_pull_timeout=Tarraing Am Oibríochta
config.git_gc_timeout=Teorainn Ama Oibriúcháin GC
config.log_config=Cumraíocht Logáil
config.logger_name_fmt=Logálaí: %s
config.disabled_logger=Díchumasaithe
config.access_log_mode=Mód Logáil Rochtana
config.access_log_template=Teimpléad Logáil Rochtana
config.xorm_log_sql=Logáil SQL
config.set_setting_failed=Theip ar shocrú %s a shocrú
monitor.stats=Staitisticí
monitor.cron=Tascanna Cron
monitor.name=Ainm
monitor.schedule=Sceideal
monitor.next=An chéad uair eile
monitor.previous=Am Roimhe Seo
monitor.execute_times=Forghníomhaíochtaí
monitor.process=Próisis reatha
monitor.stacktrace=Rian cruachta
monitor.processes_count=Próisis %d
monitor.download_diagnosis_report=Íoslódáil tuairisc diagnóis
monitor.desc=Cur síos monitor.desc=Cur síos
monitor.start=Am Tosaigh
monitor.execute_time=Am Forghníomhaithe
monitor.last_execution_result=Toradh
monitor.process.cancel=Cealaigh próiseas
monitor.process.cancel_desc=Má chuirtear próiseas ar ceal d'fhéadfadh go gcaillfí sonraí
monitor.process.cancel_notices=Cealaigh: <strong>%s</strong>?
monitor.process.children=Leanaí
monitor.queues=Scuaineanna
monitor.queue=Scuaine: %s
monitor.queue.name=Ainm
monitor.queue.type=Cineál
monitor.queue.exemplar=Cineál Eiseamláire
monitor.queue.numberworkers=Líon na nOibrithe
monitor.queue.activeworkers=Oibrithe Gníomhacha
monitor.queue.maxnumberworkers=Líon Uasta na nOibrithe
monitor.queue.numberinqueue=Uimhir i scuaine
monitor.queue.review_add=Athbhreithniú / Cuir Oibrithe leis
monitor.queue.settings.title=Socruithe Linn
monitor.queue.settings.desc=Fásann linnte go dinimiciúil mar fhreagra ar a gcuid scuaine oibrithe a bhlocáil.
monitor.queue.settings.maxnumberworkers=Uaslíon na n-oibrithe
monitor.queue.settings.maxnumberworkers.placeholder=Faoi láthair %[1]d
monitor.queue.settings.maxnumberworkers.error=Caithfidh uaslíon na n-oibrithe a bheith ina uimhir
monitor.queue.settings.submit=Nuashonrú Socruithe monitor.queue.settings.submit=Nuashonrú Socruithe
monitor.queue.settings.changed=Socruithe Nuashonraithe
monitor.queue.settings.remove_all_items=Bain gach
monitor.queue.settings.remove_all_items_done=Baineadh na míreanna go léir sa scuaine.
notices.system_notice_list=Fógraí Córais notices.system_notice_list=Fógraí Córais
notices.view_detail_header=Féach ar Sonraí Fógra
notices.operations=Oibríochtaí notices.operations=Oibríochtaí
notices.select_all=Roghnaigh Gach
notices.deselect_all=Díroghnaigh Gach
notices.inverse_selection=Roghnú Inbhéartha
notices.delete_selected=Scrios Roghnaithe
notices.delete_all=Scrios Gach Fógra
notices.type=Cineál
notices.type_1=Stóras
notices.type_2=Tasc
notices.desc=Cur síos notices.desc=Cur síos
notices.op=Oibríocht.
notices.delete_success=Scriosadh na fógraí córais.
self_check.no_problem_found=Níor aimsíodh aon fhadhb fós.
self_check.startup_warnings=Rabhadh tosaithe:
self_check.database_collation_mismatch=Bí ag súil le comhthiomsú a úsáid sa bhunachar sonraí: %s
self_check.database_collation_case_insensitive=Tá bunachar sonraí ag baint úsáide as comparáid %s, arb é comhdhlúthú neamhíogair. Cé go bhféadfadh Gitea oibriú leis, d'fhéadfadh go mbeadh roinnt cásanna annamh ann nach n-oibríonn mar a bhíothas ag súil leis.
self_check.database_inconsistent_collation_columns=Tá comhthiomsú %s in úsáid ag an mbunachar sonraí, ach tá comhthiomsuithe mímheaitseála á n-úsáid ag na colúin seo. D'fhéadfadh sé a bheith ina chúis le roinnt fadhbanna gan choinne.
self_check.database_fix_mysql=D'úsáideoirí MySQL/MariaDB, d'fhéadfá an t-ordú "gitea doctor convert" a úsáid chun na fadhbanna comhthiomsaithe a réiteach, nó d'fhéadfá an fhadhb a réiteach trí "ALTER ... COLLATE ..." SQLs de láimh freisin.
self_check.database_fix_mssql=I gcás úsáideoirí MSSQL, ní fhéadfá an fhadhb a réiteach ach trí "ALTER ... COLLATE ..." SQLs de láimh faoi láthair.
self_check.location_origin_mismatch=Ní mheaitseálann an URL reatha (%[1]s) an URL atá le feiceáil ag Gitea (%[2]s). Má tá seachfhreastalaí droim ar ais á úsáid agat, cinntigh le do thoil go bhfuil na ceanntásca "Óstríomhaire" agus "X-Forwarded-Proto" socraithe i gceart.
[action] [action]
create_repo=stóras cruthaithe <a href="%s">%s</a>
rename_repo=stóras athainmnithe ó <code>%[1]s</code> go <a href="%[2]s">%[3]s</a>
commit_repo=brú chuig <a href="%[2]s">%[3]s</a> ag <a href="%[1]s">%[4]s</a>
create_issue=`osclaíodh ceist <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue=`eagrán dúnta <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue=`athoscailt an cheist <a href="%[1]s">%[3]s#%[2]s</a>`
create_pull_request=`iarratas tarraingthe cruthaithe <a href="%[1]s">%[3]s#%[2]s</a>`
close_pull_request=`iarratas tarraingthe dúnta <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_pull_request=`iarratas tarraingthe athoscailte <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue=`trácht ar cheist <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull=`déan trácht ar iarratas tarraingthe <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request=`iarratas tarraingthe cumaisc <a href="%[1]s">%[3]s#%[2]s</a>`
auto_merge_pull_request=`iarratas tarraingthe cumasctha go huathoibríoch <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo=aistrithe stóras <code>%s</code> go <a href="%s">%s</a>
push_tag=brú <a href="%[2]s">%[3]s</a> go <a href="%[1]s">%[4]s</a>
delete_tag=scriosta clib %[2]s ó <a href="%[1]s">%[3]s</a>
delete_branch=brainse scriosta %[2]s ó <a href="%[1]s">%[3]s</a>
compare_branch=Déan comparáid
compare_commits=Déan comparáid idir tiomáintí %d
compare_commits_general=Déan comparáid idir tiomáintí
mirror_sync_push=geallann synced do <a href="%[2]s">%[3]s</a> ag <a href="%[1]s">%[4]s</a> ón scáthán
mirror_sync_create=sioncronaigh tagairt nua <a href="%[2]s">%[3]s</a> do <a href="%[1]s">%[4]s</a> ón scáthán
mirror_sync_delete=sioncronaithe agus scriosta an tagairt <code>%[2]s</code> ag <a href="%[1]s">%[3]s</a> ón scáthán
approve_pull_request=`ceadaithe <a href="%[1]s">%[3]s#%[2]s</a>`
reject_pull_request=`athruithe molta le haghaidh <a href="%[1]s">%[3]s#%[2]s</a>`
publish_release=`scaoileadh <a href="%[2]s">%[4]s</a> ag <a href="%[1]s">%[3]s</a>`
review_dismissed=`léirmheas ó <b>%[4]s</b> le haghaidh <a href="%[1]s">%[3]s#%[2]s</a>`
review_dismissed_reason=Cúis:
create_branch=brainse cruthaithe <a href="%[2]s">%[3]s</a> i <a href="%[1]s">%[4]s</a>
starred_repo=le <a href="%[1]s">%[2]s</a> le réalta
watched_repo=thosaigh sé ag breathnú ar <a href="%[1]s">%[2]s</a>
[tool] [tool]
now=anois
future=todhchaí
1s=1 soicind
1m=1 nóiméad
1h=1 uair an chloig
1d=1 lá
1w=1 seachtain
1mon=1 mhí
1y=1 bhliain
seconds=%d soicind
minutes=%d nóiméad
hours=%d uair an chloig
days=%d laethanta
weeks=%d seachtain
months=%d míonna
years=%d bliain
raw_seconds=soicind
raw_minutes=nóiméad
[dropzone] [dropzone]
default_message=Scaoil comhaid nó cliceáil anseo chun iad a uaslódáil.
invalid_input_type=Ní féidir leat comhaid den chineál seo a uaslódáil.
file_too_big=Sáraíonn méid comhaid ({{filesize}} MB) an t-uasmhéid de ({{maxFilesize}} MB).
remove_file=Bain an comhad
[notification] [notification]
notifications=Fógraí
unread=Gan léamh
read=Léigh
no_unread=Gan aon fhógraí neamh-léite.
no_read=Gan aon fhógraí léite.
pin=Fógra bioráin
mark_as_read=Marcáil mar léite
mark_as_unread=Marcáil mar neamh-léite
mark_all_as_read=Marcáil gach ceann mar léite
subscriptions=Síntiúis
watching=Ag féachaint
no_subscriptions=Gan síntiúis
[gpg] [gpg]
default_key=Sínithe leis an eochair réamhshocraithe
error.extract_sign=Theip ar an síniú a bhaint
error.generate_hash=Theip ar hash gealltanas a ghiniúint
error.no_committer_account=Níl aon chuntas nasctha le seoladh ríomhphoist an tiomnóra
error.no_gpg_keys_found=Níor aimsíodh aon eochair aithne don síniú seo sa bhunachar
error.not_signed_commit=Ní tiomantas sínithe
error.failed_retrieval_gpg_keys=Theip ar aisghabháil eochair ar bith a bhí ceangailte le cuntas an tiomnóra
error.probable_bad_signature=RABHADH! Cé go bhfuil eochair leis an ID seo sa bhunachar sonraí ní fhíoraíonn sé an tiomantas seo! Tá an tiomantas seo AMHRASACH.
error.probable_bad_default_signature=RABHADH! Cé go bhfuil an t-aitheantas seo ag an eochair réamhshocraithe ní fíoraíonn sé an tiomantas seo! Tá an tiomantas seo AMHRASACH.
[units] [units]
unit=Aonad
error.no_unit_allowed_repo=Níl cead agat rochtain a fháil ar aon chuid den tiomantas seo.
error.unit_not_allowed=Níl cead agat an rannán stóras seo a rochtain.
[packages] [packages]
title=Pacáistí
desc=Bainistigh pacáistí stórais.
empty=Níl aon phacáistí ann fós.
no_metadata=Gan aon mheiteashonraí.
empty.documentation=Le haghaidh tuilleadh eolais ar chlárlann na bpacáistí, féach ar <a target="_blank" rel="noopener noreferrer" href="%s">na doiciméid</a>.
empty.repo=An ndearna tú uaslódáil ar phacáiste, ach nach bhfuil sé léirithe anseo? Téigh go <a href="%[1]s">socruithe pacáiste</a> agus nasc leis an stóras seo é.
registry.documentation=Le haghaidh tuilleadh eolais ar chlárlann %s, féach ar <a target="_blank" rel="noopener noreferrer" href="%s">na doiciméid</a>.
filter.type=Cineál
filter.type.all=Gach
filter.no_result=Níor thug do scagaire aon torthaí.
filter.container.tagged=Clibeáilte
filter.container.untagged=Gan chlib
published_by=Foilsithe %[1]s ag <a href="%[2]s">%[3]s</a>
published_by_in=Foilsithe ag %[1]s ag <a href="%[2]s">%[3]s</a> in <a href="%[4]s"><strong>%[5]s</strong></a>
installation=Suiteáil
about=Maidir leis an bpacáiste seo
requirements=Riachtanais
dependencies=Spleithiúlachtaí
keywords=Eochairfhocail
details=Sonraí
details.author=Údar
details.project_site=Suíomh an Tionscadail
details.repository_site=Suíomh Stóras
details.documentation_site=Suíomh Doiciméadaithe
details.license=Ceadúnas
assets=Sócmhainní
versions=Leaganacha
versions.view_all=Féach ar gach
dependency.id=ID
dependency.version=Leagan
alpine.registry=Socraigh an chlár seo tríd an url a chur i do chomhad <code>/etc/apk/repositories</code>:
alpine.registry.key=Íoslódáil eochair RSA poiblí na clárlainne isteach san fhillteán <code>/etc/apk/keys/</code> chun an síniú innéacs a fhíorú:
alpine.registry.info=Roghnaigh $branch agus $repository ón liosta thíos.
alpine.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
alpine.repository=Eolas Stórais
alpine.repository.branches=Brainsí alpine.repository.branches=Brainsí
alpine.repository.repositories=Stórais alpine.repository.repositories=Stórais
alpine.repository.architectures=Ailtireachtaí
cargo.registry=Socraigh an clárlann seo sa chomhad cumraíochta lasta (mar shampla <code>~/.cargo/config.toml</code>):
cargo.install=Chun an pacáiste a shuiteáil ag baint úsáide as Cargo, reáchtáil an t-ordú seo a leanas:
chef.registry=Socraigh an clárlann seo i do chomhad <code>~/.chef/config.rb</code>:
chef.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
composer.registry=Socraigh an chlár seo i do chomhad <code>~/.composer/config.json</code>:
composer.install=Chun an pacáiste a shuiteáil ag baint úsáide as Cumadóir, reáchtáil an t-ordú seo a leanas:
composer.dependencies=Spleithiúlachtaí
composer.dependencies.development=Spleithiúlachtaí Forbartha
conan.details.repository=Stóras
conan.registry=Socraigh an clárlann seo ón líne ordaithe:
conan.install=Chun an pacáiste a shuiteáil ag úsáid Conan, reáchtáil an t-ordú seo a leanas:
conda.registry=Socraigh an chlár seo mar stóras Conda i do chomhad <code>.condarc</code>:
conda.install=Chun an pacáiste a shuiteáil ag úsáid Conda, reáchtáil an t-ordú seo a leanas:
container.details.type=Cineál Íomhá
container.details.platform=Ardán
container.pull=Tarraing an íomhá ón líne ordaithe:
container.digest=Díleáigh:
container.multi_arch=Córas Oibriúcháin / Ailtireacht
container.layers=Sraitheanna Íomhá
container.labels=Lipéid
container.labels.key=Eochair
container.labels.value=Luach
cran.registry=Cumraigh an chlárlann seo i do chomhad <code>Rprofile.site</code>:
cran.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
debian.registry=Socraigh an clárlann seo ón líne ordaithe:
debian.registry.info=Roghnaigh $distribution agus $component ón liosta thíos.
debian.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
debian.repository=Eolas Stóras
debian.repository.distributions=Dáiltí
debian.repository.components=Comhpháirteanna
debian.repository.architectures=Ailtireachtaí
generic.download=Íoslódáil pacáiste ón líne ordaithe:
go.install=Suiteáil an pacáiste ón líne ordaithe:
helm.registry=Socraigh an clárlann seo ón líne ordaithe:
helm.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
maven.registry=Socraigh an clárlann seo i do chomhad <code>pom.xml</code> tionscadail:
maven.install=Chun an pacáiste a úsáid cuir na nithe seo a leanas sa bhloc <code>spleáchais</code> sa chomhad <code>pom.xml</code>:
maven.install2=Rith tríd an líne ordaithe:
maven.download=Chun an spleáchas a íoslódáil, rith tríd an líne ordaithe:
nuget.registry=Socraigh an clárlann seo ón líne ordaithe:
nuget.install=Chun an pacáiste a shuiteáil ag úsáid NuGet, reáchtáil an t-ordú seo a leanas:
nuget.dependency.framework=Spriocchreat
npm.registry=Socraigh an chlárlann seo i do chomhad <code>.npmrc</code> do thionscadail:
npm.install=Chun an pacáiste a shuiteáil ag úsáid npm, reáchtáil an t-ordú seo a leanas:
npm.install2=nó cuir leis an gcomhad package.json é:
npm.dependencies=Spleithiúlachtaí
npm.dependencies.development=Spleithiúlachtaí Forbartha
npm.dependencies.bundle=Spleáchais Chuachta
npm.dependencies.peer=Spleithiúlachtaí Piaraí
npm.dependencies.optional=Spleáchais Roghnacha
npm.details.tag=Clib
pub.install=Chun an pacáiste a shuiteáil ag úsáid Dart, reáchtáil an t-ordú seo a leanas:
pypi.requires=Teastaíonn Python
pypi.install=Chun an pacáiste a shuiteáil ag úsáid pip, reáchtáil an t-ordú seo a leanas:
rpm.registry=Socraigh an clárlann seo ón líne ordaithe:
rpm.distros.redhat=ar dháileadh bunaithe ar RedHat
rpm.distros.suse=ar dháileadh bunaithe ar SUSE
rpm.install=Chun an pacáiste a shuiteáil, rith an t-ordú seo a leanas:
rpm.repository=Eolas Stóras
rpm.repository.architectures=Ailtireachtaí
rpm.repository.multiple_groups=Tá an pacáiste seo ar fáil i ngrúpaí éagsúla.
rubygems.install=Chun an pacáiste a shuiteáil ag baint úsáide as gem, reáchtáil an t-ordú seo a leanas:
rubygems.install2=nó cuir leis an Gemfile é:
rubygems.dependencies.runtime=Spleáchais Rith-Ama
rubygems.dependencies.development=Spleáchais Forbartha
rubygems.required.ruby=Éilíonn leagan Ruby
rubygems.required.rubygems=Éilíonn leagan RubyGem
swift.registry=Socraigh an clárlann seo ón líne ordaithe:
swift.install=Cuir an pacáiste i do <code>chomhad Package.swift</code>:
swift.install2=agus reáchtáil an t-ordú seo a leanas:
vagrant.install=Chun bosca Vagrant a chur leis, reáchtáil an t-ordú seo a leanas:
settings.link=Nasc an pacáiste seo le stóras
settings.link.description=Má nascann tú pacáiste le stóras, liostaítear an pacáiste i liosta pacáistí an stórais.
settings.link.select=Roghnaigh Stóras
settings.link.button=Nuashonraigh Nasc Stórais
settings.link.success=D'éirigh le nasc an stórais a nuashonrú.
settings.link.error=Theip ar an nasc stóras a nuashonrú.
settings.delete=Scrios pacáiste
settings.delete.description=Tá pacáiste a scriosadh buan agus ní féidir é a chur ar ais.
settings.delete.notice=Tá tú ar tí %s (%s) a scriosadh. Tá an oibríocht seo dochúlaithe, an bhfuil tú cinnte?
settings.delete.success=Tá an pacáiste scriosta.
settings.delete.error=Theip ar an pacáiste a scriosadh.
owner.settings.cargo.title=Innéacs Clárlann Lasta
owner.settings.cargo.initialize=Innéacs a chur i dtosach
owner.settings.cargo.initialize.description=Tá gá le stóras innéacs speisialta Git chun an clárlann Cargo a úsáid. Tríd an rogha seo, cruthófar an stóras (nó athchruthófar é) agus cumrófar é go huathoibríoch.
owner.settings.cargo.initialize.error=Níorbh fhéidir an t-innéacs Cargo a thúsú: %v
owner.settings.cargo.initialize.success=Cruthaíodh an t-innéacs Cargo go rathúil.
owner.settings.cargo.rebuild=Innéacs Atógáil
owner.settings.cargo.rebuild.description=Is féidir atógáil a bheith úsáideach mura bhfuil an t-innéacs sioncronaithe leis na pacáistí Cargo stóráilte.
owner.settings.cargo.rebuild.error=Níorbh fhéidir an t-innéacs Cargo a atógáil: %v
owner.settings.cargo.rebuild.success=D'éirigh leis an innéacs Cargo a atógáil.
owner.settings.cleanuprules.title=Bainistigh Rialacha Glanta
owner.settings.cleanuprules.add=Cuir Riail Glantacháin leis
owner.settings.cleanuprules.edit=Cuir Riail Glantacháin in eagar
owner.settings.cleanuprules.none=Níl aon rialacha glanta ar fáil. Féach ar na doiciméid le do thoil.
owner.settings.cleanuprules.preview=Réamhamharc Riail Glantacháin
owner.settings.cleanuprules.preview.overview=Tá pacáistí %d beartaithe a bhaint.
owner.settings.cleanuprules.preview.none=Ní hionann riail glantacháin agus pacáistí ar bith.
owner.settings.cleanuprules.enabled=Cumasaithe
owner.settings.cleanuprules.pattern_full_match=Cuir patrún i bhfeidhm ar ainm an phacáiste iomlán
owner.settings.cleanuprules.keep.title=Coinnítear leaganacha a mheaitseálann leis na rialacha seo, fiú má mheaitseálann siad riail bhaint thíos.
owner.settings.cleanuprules.keep.count=Coinnigh an ceann is déanaí
owner.settings.cleanuprules.keep.count.1=1 leagan in aghaidh an phacáiste
owner.settings.cleanuprules.keep.count.n=Leaganacha %d in aghaidh an phacáiste
owner.settings.cleanuprules.keep.pattern=Coinnigh leaganacha meaitseála
owner.settings.cleanuprules.keep.pattern.container=Coinnítear an leagan <code>is déanaí</code> le haghaidh pacáistí Coimeádán i gcónaí.
owner.settings.cleanuprules.remove.title=Baintear leaganacha a mheaitseálann leis na rialacha seo, mura deir riail thuas iad a choinneáil.
owner.settings.cleanuprules.remove.days=Bain leaganacha níos sine ná
owner.settings.cleanuprules.remove.pattern=Bain leaganacha meaitseála
owner.settings.cleanuprules.success.update=Nuashonraíodh an riail ghlantacháin.
owner.settings.cleanuprules.success.delete=Scriosadh an riail glantacháin.
owner.settings.chef.title=Clárlann Chef
owner.settings.chef.keypair=Gin péire eochair
owner.settings.chef.keypair.description=Tá eochairphéire riachtanach le fíordheimhniú a dhéanamh ar chlárlann an Chef. Má tá péire eochrach ginte agat roimhe seo, má ghinfidh tú eochairphéire nua, scriosfar an seanphéire eochair.
[secrets] [secrets]
secrets=Rúin
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
none=Níl aon rúin ann fós.
creation=Cuir Rúnda leis
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
creation.success=Tá an rún "%s" curtha leis.
creation.failed=Theip ar an rún a chur leis.
deletion=Bain rún
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
deletion.success=Tá an rún bainte.
deletion.failed=Theip ar rún a bhaint.
management=Bainistíocht Rúin
[actions] [actions]
actions=Gníomhartha
unit.desc=Bainistigh gníomhartha
status.unknown=Anaithnid
status.waiting=Ag fanacht
status.running=Ag rith
status.success=Rath
status.failure=Teip
status.cancelled=Cealaíodh
status.skipped=Scipeáilte
status.blocked=Blocáilte
runners=Reathaitheoirí
runners.runner_manage_panel=Bainistíocht reathaithe
runners.new=Cruthaigh reathaí nua
runners.new_notice=Conas reathaí a thosú
runners.status=Stádas
runners.id=ID
runners.name=Ainm
runners.owner_type=Cineál
runners.description=Cur síos runners.description=Cur síos
runners.labels=Lipéid
runners.last_online=Am Ar Líne Deiridh
runners.runner_title=Reathaí
runners.task_list=Tascanna le déanaí ar an reathaí seo
runners.task_list.no_tasks=Níl aon tasc ann fós.
runners.task_list.run=Rith runners.task_list.run=Rith
runners.task_list.status=Stádas
runners.task_list.repository=Stóras
runners.task_list.commit=Tiomantas runners.task_list.commit=Tiomantas
runners.task_list.done_at=Déanta ag
runners.edit_runner=Cuir Reathaí in Eagar
runners.update_runner=Nuashonrú Athruithe
runners.update_runner_success=Nuashonraíodh an Reathaí
runners.update_runner_failed=Theip ar an reathaí a nuashonrú
runners.delete_runner=Scrios an reathaí seo
runners.delete_runner_success=Scriosadh an reathaí go rathúil
runners.delete_runner_failed=Theip ar an reathaí a scriosadh
runners.delete_runner_header=Deimhnigh an reathaí seo a scriosadh
runners.delete_runner_notice=Má tá tasc ar siúl ar an reathaí seo, cuirfear deireadh leis agus marcáil mar theip. Féadfaidh sé sreabhadh oibre tógála a bhriseadh.
runners.none=Níl aon reathaí ar fáil
runners.status.unspecified=Anaithnid
runners.status.idle=Díomhaoin
runners.status.active=Gníomhach runners.status.active=Gníomhach
runners.status.offline=As líne
runners.version=Leagan
runners.reset_registration_token=Athshocraigh comhartha clár runners.reset_registration_token=Athshocraigh comhartha clár
runners.reset_registration_token_success=D'éirigh le hathshocrú comhartha clárúcháin an dara háit runners.reset_registration_token_success=D'éirigh le hathshocrú comhartha clárúcháin an dara háit
@ -2974,11 +3700,54 @@ runs.commit=Tiomantas
runs.scheduled=Sceidealaithe runs.scheduled=Sceidealaithe
runs.pushed_by=bhrú ag runs.pushed_by=bhrú ag
runs.invalid_workflow_helper=Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s runs.invalid_workflow_helper=Tá comhad cumraíochta sreabhadh oibre nebhailí. Seiceáil do chomhad cumraithe le do thoil: %s
runs.no_matching_online_runner_helper=Gan aon reathaí ar líne a mheaitseáil le lipéad: %s
runs.no_job_without_needs=Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre gan spleáchas.
runs.no_job=Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre
runs.actor=Aisteoir
runs.status=Stádas
runs.actors_no_select=Gach aisteoir
runs.status_no_select=Gach stádas
runs.no_results=Níor mheaitseáil aon torthaí.
runs.no_workflows=Níl aon sreafaí oibre ann fós.
runs.no_workflows.quick_start=Níl a fhios agam conas tosú le Gitea Actions? Féach <a target="_blank" rel="noopener noreferrer" href="%s">an treoirleabhar mear tosaithe</a>.
runs.no_workflows.documentation=Le haghaidh tuilleadh eolais ar Gitea Actions, féach ar <a target="_blank" rel="noopener noreferrer" href="%s">na doiciméid</a>.
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
workflow.disable=Díchumasaigh sreabhadh oibre
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
workflow.enable=Cumasaigh sreabhadh oibre
workflow.enable_success=Cumasaíodh sreabhadh oibre '%s' go rathúil.
workflow.disabled=Tá sreabhadh oibre díchumasaithe
workflow.run=Rith Sreabhadh Oibre
workflow.not_found=Níor aimsíodh sreabhadh oibre '%s'.
workflow.run_success=Ritheann sreabhadh oibre '%s' go rathúil.
workflow.from_ref=Úsáid sreabhadh oibre ó
workflow.has_workflow_dispatch=Tá comhoibriú ag an gcur i bhfeidhm seo le himeacht workflow_dispatch.
need_approval_desc=Teastaíonn faomhadh chun sreafaí oibre a rith le haghaidh iarratas tarraingt forc.
variables=Athróga
variables.management=Bainistíocht Athróg
variables.creation=Cuir Athróg leis
variables.none=Níl aon athróga ann fós.
variables.deletion=Bain athróg
variables.deletion.description=Tá athróg a bhaint buan agus ní féidir é a chur ar ais. Lean ar aghaidh?
variables.description=Cuirfear athróga chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt eile.
variables.id_not_exist=Níl athróg le ID %d ann.
variables.edit=Cuir Athróg in Eagar
variables.deletion.failed=Theip ar athróg a bhaint.
variables.deletion.success=Tá an athróg bainte.
variables.creation.failed=Theip ar athróg a chur leis.
variables.creation.success=Tá an athróg "%s" curtha leis.
variables.update.failed=Theip ar athróg a chur in eagar.
variables.update.success=Tá an t-athróg curtha in eagar.
[projects] [projects]
deleted.display_name=Tionscadal scriosta
type-1.display_name=Tionscadal Aonair
type-2.display_name=Tionscadal Stórais
type-3.display_name=Tionscadal Eagrúcháin type-3.display_name=Tionscadal Eagrúcháin
[git.filemode] [git.filemode]

View file

@ -63,6 +63,20 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin") ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
return return
} }
// check if scope only applies to public resources
publicOnly, err := scope.PublicOnly()
if err != nil {
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
return
}
if publicOnly {
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
return
}
}
} }
} }

View file

@ -10,11 +10,11 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"sync"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container" container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
container_module "code.gitea.io/gitea/modules/packages/container" container_module "code.gitea.io/gitea/modules/packages/container"
@ -22,8 +22,6 @@ import (
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )
var uploadVersionMutex sync.Mutex
// saveAsPackageBlob creates a package blob from an upload // saveAsPackageBlob creates a package blob from an upload
// The uploaded blob gets stored in a special upload version to link them to the package/image // The uploaded blob gets stored in a special upload version to link them to the package/image
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
@ -90,13 +88,20 @@ func mountBlob(ctx context.Context, pi *packages_service.PackageInfo, pb *packag
}) })
} }
func containerPkgName(piOwnerID int64, piName string) string {
return fmt.Sprintf("pkg_%d_container_%s", piOwnerID, strings.ToLower(piName))
}
func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) { func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageInfo) (*packages_model.PackageVersion, error) {
var uploadVersion *packages_model.PackageVersion var uploadVersion *packages_model.PackageVersion
// FIXME: Replace usage of mutex with database transaction releaser, err := globallock.Lock(ctx, containerPkgName(pi.Owner.ID, pi.Name))
// https://github.com/go-gitea/gitea/pull/21862 if err != nil {
uploadVersionMutex.Lock() return nil, err
err := db.WithTx(ctx, func(ctx context.Context) error { }
defer releaser()
err = db.WithTx(ctx, func(ctx context.Context) error {
created := true created := true
p := &packages_model.Package{ p := &packages_model.Package{
OwnerID: pi.Owner.ID, OwnerID: pi.Owner.ID,
@ -140,7 +145,6 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
return nil return nil
}) })
uploadVersionMutex.Unlock()
return uploadVersion, err return uploadVersion, err
} }
@ -173,6 +177,12 @@ func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, p
} }
func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error { func deleteBlob(ctx context.Context, ownerID int64, image, digest string) error {
releaser, err := globallock.Lock(ctx, containerPkgName(ownerID, image))
if err != nil {
return err
}
defer releaser()
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{ pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
OwnerID: ownerID, OwnerID: ownerID,

View file

@ -45,7 +45,7 @@ func ListHooks(ctx *context.APIContext) {
} }
hooks := make([]*api.Hook, len(sysHooks)) hooks := make([]*api.Hook, len(sysHooks))
for i, hook := range sysHooks { for i, hook := range sysHooks {
h, err := webhook_service.ToHook(setting.AppURL+"/admin", hook) h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", hook)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
return return
@ -83,7 +83,7 @@ func GetHook(ctx *context.APIContext) {
} }
return return
} }
h, err := webhook_service.ToHook("/admin/", hook) h, err := webhook_service.ToHook("/-/admin/", hook)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
return return

View file

@ -235,6 +235,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
} }
} }
func checkTokenPublicOnly() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.PublicOnly {
return
}
requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
if !ok || len(requiredScopeCategories) == 0 {
return
}
// public Only permission check
switch {
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
return
}
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
return
}
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
return
}
}
}
}
// if a token is being used for auth, we check that it contains the required scope // if a token is being used for auth, we check that it contains the required scope
// if a token is not being used, reqToken will enforce other sign in methods // if a token is not being used, reqToken will enforce other sign in methods
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) { func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
return return
} }
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
// use the http method to determine the access level // use the http method to determine the access level
requiredScopeLevel := auth_model.Read requiredScopeLevel := auth_model.Read
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" { if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
@ -261,6 +314,18 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
// get the required scope for the given access level and category // get the required scope for the given access level and category
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...) requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
allow, err := scope.HasScope(requiredScopes...)
if err != nil {
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
return
}
if !allow {
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
return
}
ctx.Data["requiredScopeCategories"] = requiredScopeCategories
// check if scope only applies to public resources // check if scope only applies to public resources
publicOnly, err := scope.PublicOnly() publicOnly, err := scope.PublicOnly()
@ -269,21 +334,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
return return
} }
// this context is used by the middleware in the specific route // assign to true so that those searching should only filter public repositories/users/organizations
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository) ctx.PublicOnly = publicOnly
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
allow, err := scope.HasScope(requiredScopes...)
if err != nil {
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
return
}
if allow {
return
}
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
} }
} }
@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) {
return return
} }
if true == ctx.Data["IsApiToken"] {
publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
if pubRepoExists && publicRepo.(bool) &&
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
return
}
if pubOrgExists && publicOrg.(bool) &&
ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
return
}
return
}
if ctx.IsSigned { if ctx.IsSigned {
return return
} }
@ -879,11 +912,11 @@ func Routes() *web.Router {
m.Group("/user/{username}", func() { m.Group("/user/{username}", func() {
m.Get("", activitypub.Person) m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context.UserAssignmentAPI()) }, context.UserAssignmentAPI(), checkTokenPublicOnly())
m.Group("/user-id/{user-id}", func() { m.Group("/user-id/{user-id}", func() {
m.Get("", activitypub.Person) m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context.UserIDAssignmentAPI()) }, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
} }
@ -939,7 +972,7 @@ func Routes() *web.Router {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth()) }, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds) m.Get("/activities/feeds", user.ListUserActivityFeeds)
}, context.UserAssignmentAPI(), individualPermsChecker) }, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope) // Users (requires user scope)
@ -957,7 +990,7 @@ func Routes() *web.Router {
m.Get("/starred", user.GetStarredRepos) m.Get("/starred", user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos) m.Get("/subscriptions", user.GetWatchedRepos)
}, context.UserAssignmentAPI()) }, context.UserAssignmentAPI(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Users (requires user scope) // Users (requires user scope)
@ -1044,7 +1077,7 @@ func Routes() *web.Router {
m.Get("", user.IsStarring) m.Get("", user.IsStarring)
m.Put("", user.Star) m.Put("", user.Star)
m.Delete("", user.Unstar) m.Delete("", user.Unstar)
}, repoAssignment()) }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
m.Get("/times", repo.ListMyTrackedTimes) m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/stopwatches", repo.GetStopwatches) m.Get("/stopwatches", repo.GetStopwatches)
@ -1069,18 +1102,20 @@ func Routes() *web.Router {
m.Get("", user.CheckUserBlock) m.Get("", user.CheckUserBlock)
m.Put("", user.BlockUser) m.Put("", user.BlockUser)
m.Delete("", user.UnblockUser) m.Delete("", user.UnblockUser)
}, context.UserAssignmentAPI()) }, context.UserAssignmentAPI(), checkTokenPublicOnly())
}) })
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Repositories (requires repo scope, org scope) // Repositories (requires repo scope, org scope)
m.Post("/org/{org}/repos", m.Post("/org/{org}/repos",
// FIXME: we need org in context
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
reqToken(), reqToken(),
bind(api.CreateRepoOption{}), bind(api.CreateRepoOption{}),
repo.CreateOrgRepoDeprecated) repo.CreateOrgRepoDeprecated)
// requires repo scope // requires repo scope
// FIXME: Don't expose repository id outside of the system
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID) m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
// Repos (requires repo scope) // Repos (requires repo scope)
@ -1334,7 +1369,7 @@ func Routes() *web.Router {
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar) m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
m.Delete("", repo.DeleteAvatar) m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken()) }, reqAdmin(), reqToken())
}, repoAssignment()) }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
// Notifications (requires notifications scope) // Notifications (requires notifications scope)
@ -1343,7 +1378,7 @@ func Routes() *web.Router {
m.Combo("/notifications", reqToken()). m.Combo("/notifications", reqToken()).
Get(notify.ListRepoNotifications). Get(notify.ListRepoNotifications).
Put(notify.ReadRepoNotifications) Put(notify.ReadRepoNotifications)
}, repoAssignment()) }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
// Issue (requires issue scope) // Issue (requires issue scope)
@ -1457,7 +1492,7 @@ func Routes() *web.Router {
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone) Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
}) })
}, repoAssignment()) }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
@ -1468,14 +1503,14 @@ func Routes() *web.Router {
m.Get("/files", reqToken(), packages.ListPackageFiles) m.Get("/files", reqToken(), packages.ListPackageFiles)
}) })
m.Get("/", reqToken(), packages.ListPackages) m.Get("/", reqToken(), packages.ListPackages)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
// Organizations // Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
m.Group("/users/{username}/orgs", func() { m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs) m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions) m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create) m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization)) m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
m.Group("/orgs/{org}", func() { m.Group("/orgs/{org}", func() {
@ -1533,7 +1568,7 @@ func Routes() *web.Router {
m.Delete("", org.UnblockUser) m.Delete("", org.UnblockUser)
}) })
}, reqToken(), reqOrgOwnership()) }, reqToken(), reqOrgOwnership())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
m.Group("/teams/{teamid}", func() { m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam). m.Combo("").Get(reqToken(), org.GetTeam).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
@ -1553,7 +1588,7 @@ func Routes() *web.Router {
Get(reqToken(), org.GetTeamRepo) Get(reqToken(), org.GetTeamRepo)
}) })
m.Get("/activities/feeds", org.ListTeamActivityFeeds) m.Get("/activities/feeds", org.ListTeamActivityFeeds)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
m.Group("/admin", func() { m.Group("/admin", func() {
m.Group("/cron", func() { m.Group("/cron", func() {

View file

@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) {
// "$ref": "#/responses/OrganizationList" // "$ref": "#/responses/OrganizationList"
vMode := []api.VisibleType{api.VisibleTypePublic} vMode := []api.VisibleType{api.VisibleTypePublic}
if ctx.IsSigned { if ctx.IsSigned && !ctx.PublicOnly {
vMode = append(vMode, api.VisibleTypeLimited) vMode = append(vMode, api.VisibleTypeLimited)
if ctx.Doer.IsAdmin { if ctx.Doer.IsAdmin {
vMode = append(vMode, api.VisibleTypePrivate) vMode = append(vMode, api.VisibleTypePrivate)

View file

@ -41,80 +41,93 @@ func SearchIssues(ctx *context.APIContext) {
// parameters: // parameters:
// - name: state // - name: state
// in: query // in: query
// description: whether issue is open or closed // description: State of the issue
// type: string // type: string
// enum: [open, closed, all]
// default: open
// - name: labels // - name: labels
// in: query // in: query
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded // description: Comma-separated list of label names. Fetch only issues that have any of these labels. Non existent labels are discarded.
// type: string // type: string
// - name: milestones // - name: milestones
// in: query // in: query
// description: comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded // description: Comma-separated list of milestone names. Fetch only issues that have any of these milestones. Non existent milestones are discarded.
// type: string // type: string
// - name: q // - name: q
// in: query // in: query
// description: search string // description: Search string
// type: string // type: string
// - name: priority_repo_id // - name: priority_repo_id
// in: query // in: query
// description: repository to prioritize in the results // description: Repository ID to prioritize in the results
// type: integer // type: integer
// format: int64 // format: int64
// - name: type // - name: type
// in: query // in: query
// description: filter by type (issues / pulls) if set // description: Filter by issue type
// type: string // type: string
// enum: [issues, pulls]
// - name: since // - name: since
// in: query // in: query
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format // description: Only show issues updated after the given time (RFC 3339 format)
// type: string // type: string
// format: date-time // format: date-time
// required: false
// - name: before // - name: before
// in: query // in: query
// description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format // description: Only show issues updated before the given time (RFC 3339 format)
// type: string // type: string
// format: date-time // format: date-time
// required: false
// - name: assigned // - name: assigned
// in: query // in: query
// description: filter (issues / pulls) assigned to you, default is false // description: Filter issues or pulls assigned to the authenticated user
// type: boolean // type: boolean
// default: false
// - name: created // - name: created
// in: query // in: query
// description: filter (issues / pulls) created by you, default is false // description: Filter issues or pulls created by the authenticated user
// type: boolean // type: boolean
// default: false
// - name: mentioned // - name: mentioned
// in: query // in: query
// description: filter (issues / pulls) mentioning you, default is false // description: Filter issues or pulls mentioning the authenticated user
// type: boolean // type: boolean
// default: false
// - name: review_requested // - name: review_requested
// in: query // in: query
// description: filter pulls requesting your review, default is false // description: Filter pull requests where the authenticated user's review was requested
// type: boolean // type: boolean
// default: false
// - name: reviewed // - name: reviewed
// in: query // in: query
// description: filter pulls reviewed by you, default is false // description: Filter pull requests reviewed by the authenticated user
// type: boolean // type: boolean
// default: false
// - name: owner // - name: owner
// in: query // in: query
// description: filter by owner // description: Filter by repository owner
// type: string // type: string
// - name: team // - name: team
// in: query // in: query
// description: filter by team (requires organization owner parameter to be provided) // description: Filter by team (requires organization owner parameter)
// type: string // type: string
// - name: page // - name: page
// in: query // in: query
// description: page number of results to return (1-based) // description: Page number of results to return (1-based)
// type: integer // type: integer
// minimum: 1
// default: 1
// - name: limit // - name: limit
// in: query // in: query
// description: page size of results // description: Number of items per page
// type: integer // type: integer
// minimum: 0
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/IssueList" // "$ref": "#/responses/IssueList"
// "400":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
before, since, err := context.GetQueryBeforeSince(ctx.Base) before, since, err := context.GetQueryBeforeSince(ctx.Base)
if err != nil { if err != nil {
@ -149,7 +162,7 @@ func SearchIssues(ctx *context.APIContext) {
Actor: ctx.Doer, Actor: ctx.Doer,
} }
if ctx.IsSigned { if ctx.IsSigned {
opts.Private = true opts.Private = !ctx.PublicOnly
opts.AllLimited = true opts.AllLimited = true
} }
if ctx.FormString("owner") != "" { if ctx.FormString("owner") != "" {

View file

@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
if ctx.PublicOnly {
private = false
}
opts := &repo_model.SearchRepoOptions{ opts := &repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx), ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer, Actor: ctx.Doer,
@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) {
TeamID: ctx.FormInt64("team_id"), TeamID: ctx.FormInt64("team_id"),
TopicOnly: ctx.FormBool("topic"), TopicOnly: ctx.FormBool("topic"),
Collaborate: optional.None[bool](), Collaborate: optional.None[bool](),
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")), Private: private,
Template: optional.None[bool](), Template: optional.None[bool](),
StarredByID: ctx.FormInt64("starredBy"), StarredByID: ctx.FormInt64("starredBy"),
IncludeDescription: ctx.FormBool("includeDesc"), IncludeDescription: ctx.FormBool("includeDesc"),

View file

@ -9,6 +9,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -67,12 +68,17 @@ func Search(ctx *context.APIContext) {
maxResults = 1 maxResults = 1
users = []*user_model.User{user_model.NewActionsUser()} users = []*user_model.User{user_model.NewActionsUser()}
default: default:
var visible []structs.VisibleType
if ctx.PublicOnly {
visible = []structs.VisibleType{structs.VisibleTypePublic}
}
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer, Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"), Keyword: ctx.FormTrim("q"),
UID: uid, UID: uid,
Type: user_model.UserTypeIndividual, Type: user_model.UserTypeIndividual,
SearchByEmail: true, SearchByEmail: true,
Visible: visible,
ListOptions: listOptions, ListOptions: listOptions,
}) })
if err != nil { if err != nil {

View file

@ -100,7 +100,7 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) { func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) {
hook, ok := addHook(ctx, form, 0, 0) hook, ok := addHook(ctx, form, 0, 0)
if ok { if ok {
h, err := webhook_service.ToHook(setting.AppSubURL+"/admin", hook) h, err := webhook_service.ToHook(setting.AppSubURL+"/-/admin", hook)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
return return
@ -268,7 +268,7 @@ func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID in
ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err)
return return
} }
h, err := webhook_service.ToHook(setting.AppURL+"/admin", updated) h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", updated)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
return return

View file

@ -208,7 +208,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
return return
} }
cols := make([]string, 0, len(opts.GitPushOptions)) cols := make([]string, 0, 2)
if isPrivate.Has() { if isPrivate.Has() {
repo.IsPrivate = isPrivate.Value() repo.IsPrivate = isPrivate.Value()

View file

@ -185,9 +185,9 @@ func DashboardPost(ctx *context.Context) {
} }
} }
if form.From == "monitor" { if form.From == "monitor" {
ctx.Redirect(setting.AppSubURL + "/admin/monitor/cron") ctx.Redirect(setting.AppSubURL + "/-/admin/monitor/cron")
} else { } else {
ctx.Redirect(setting.AppSubURL + "/admin") ctx.Redirect(setting.AppSubURL + "/-/admin")
} }
} }

View file

@ -23,8 +23,8 @@ var (
func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers { func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers {
return &user_setting.OAuth2CommonHandlers{ return &user_setting.OAuth2CommonHandlers{
OwnerID: 0, OwnerID: 0,
BasePathList: fmt.Sprintf("%s/admin/applications", setting.AppSubURL), BasePathList: fmt.Sprintf("%s/-/admin/applications", setting.AppSubURL),
BasePathEditPrefix: fmt.Sprintf("%s/admin/applications/oauth2", setting.AppSubURL), BasePathEditPrefix: fmt.Sprintf("%s/-/admin/applications/oauth2", setting.AppSubURL),
TplAppEdit: tplSettingsOauth2ApplicationEdit, TplAppEdit: tplSettingsOauth2ApplicationEdit,
} }
} }

View file

@ -324,7 +324,7 @@ func NewAuthSourcePost(ctx *context.Context) {
log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name)
ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name)) ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name))
ctx.Redirect(setting.AppSubURL + "/admin/auths") ctx.Redirect(setting.AppSubURL + "/-/admin/auths")
} }
// EditAuthSource render editing auth source page // EditAuthSource render editing auth source page
@ -437,7 +437,7 @@ func EditAuthSourcePost(ctx *context.Context) {
log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.update_success")) ctx.Flash.Success(ctx.Tr("admin.auths.update_success"))
ctx.Redirect(setting.AppSubURL + "/admin/auths/" + strconv.FormatInt(form.ID, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/auths/" + strconv.FormatInt(form.ID, 10))
} }
// DeleteAuthSource response for deleting an auth source // DeleteAuthSource response for deleting an auth source
@ -454,11 +454,11 @@ func DeleteAuthSource(ctx *context.Context) {
} else { } else {
ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err)) ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err))
} }
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.PathParam(":authid"))) ctx.JSONRedirect(setting.AppSubURL + "/-/admin/auths/" + url.PathEscape(ctx.PathParam(":authid")))
return return
} }
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID) log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success")) ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success"))
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths") ctx.JSONRedirect(setting.AppSubURL + "/-/admin/auths")
} }

View file

@ -40,7 +40,7 @@ func SendTestMail(ctx *context.Context) {
ctx.Flash.Info(ctx.Tr("admin.config.test_mail_sent", email)) ctx.Flash.Info(ctx.Tr("admin.config.test_mail_sent", email))
} }
ctx.Redirect(setting.AppSubURL + "/admin/config") ctx.Redirect(setting.AppSubURL + "/-/admin/config")
} }
// TestCache test the cache settings // TestCache test the cache settings
@ -56,7 +56,7 @@ func TestCache(ctx *context.Context) {
} }
} }
ctx.Redirect(setting.AppSubURL + "/admin/config") ctx.Redirect(setting.AppSubURL + "/-/admin/config")
} }
func shadowPasswordKV(cfgItem, splitter string) string { func shadowPasswordKV(cfgItem, splitter string) string {

View file

@ -134,7 +134,7 @@ func ActivateEmail(ctx *context.Context) {
ctx.Flash.Info(ctx.Tr("admin.emails.updated")) ctx.Flash.Info(ctx.Tr("admin.emails.updated"))
} }
redirect, _ := url.Parse(setting.AppSubURL + "/admin/emails") redirect, _ := url.Parse(setting.AppSubURL + "/-/admin/emails")
q := url.Values{} q := url.Values{}
if val := ctx.FormTrim("q"); len(val) > 0 { if val := ctx.FormTrim("q"); len(val) > 0 {
q.Set("q", val) q.Set("q", val)

View file

@ -36,8 +36,8 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
sys["Title"] = ctx.Tr("admin.systemhooks") sys["Title"] = ctx.Tr("admin.systemhooks")
sys["Description"] = ctx.Tr("admin.systemhooks.desc", "https://docs.gitea.com/usage/webhooks") sys["Description"] = ctx.Tr("admin.systemhooks.desc", "https://docs.gitea.com/usage/webhooks")
sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]()) sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]())
sys["BaseLink"] = setting.AppSubURL + "/admin/hooks" sys["BaseLink"] = setting.AppSubURL + "/-/admin/hooks"
sys["BaseLinkNew"] = setting.AppSubURL + "/admin/system-hooks" sys["BaseLinkNew"] = setting.AppSubURL + "/-/admin/system-hooks"
if err != nil { if err != nil {
ctx.ServerError("GetWebhooksAdmin", err) ctx.ServerError("GetWebhooksAdmin", err)
return return
@ -46,8 +46,8 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
def["Title"] = ctx.Tr("admin.defaulthooks") def["Title"] = ctx.Tr("admin.defaulthooks")
def["Description"] = ctx.Tr("admin.defaulthooks.desc", "https://docs.gitea.com/usage/webhooks") def["Description"] = ctx.Tr("admin.defaulthooks.desc", "https://docs.gitea.com/usage/webhooks")
def["Webhooks"], err = webhook.GetDefaultWebhooks(ctx) def["Webhooks"], err = webhook.GetDefaultWebhooks(ctx)
def["BaseLink"] = setting.AppSubURL + "/admin/hooks" def["BaseLink"] = setting.AppSubURL + "/-/admin/hooks"
def["BaseLinkNew"] = setting.AppSubURL + "/admin/default-hooks" def["BaseLinkNew"] = setting.AppSubURL + "/-/admin/default-hooks"
if err != nil { if err != nil {
ctx.ServerError("GetWebhooksAdmin", err) ctx.ServerError("GetWebhooksAdmin", err)
return return
@ -67,5 +67,5 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
} }
ctx.JSONRedirect(setting.AppSubURL + "/admin/hooks") ctx.JSONRedirect(setting.AppSubURL + "/-/admin/hooks")
} }

View file

@ -74,5 +74,5 @@ func EmptyNotices(ctx *context.Context) {
log.Trace("System notices deleted by admin (%s): [start: %d]", ctx.Doer.Name, 0) log.Trace("System notices deleted by admin (%s): [start: %d]", ctx.Doer.Name, 0)
ctx.Flash.Success(ctx.Tr("admin.notices.delete_success")) ctx.Flash.Success(ctx.Tr("admin.notices.delete_success"))
ctx.Redirect(setting.AppSubURL + "/admin/notices") ctx.Redirect(setting.AppSubURL + "/-/admin/notices")
} }

View file

@ -99,7 +99,7 @@ func DeletePackageVersion(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
ctx.JSONRedirect(setting.AppSubURL + "/admin/packages?page=" + url.QueryEscape(ctx.FormString("page")) + "&q=" + url.QueryEscape(ctx.FormString("q")) + "&type=" + url.QueryEscape(ctx.FormString("type"))) ctx.JSONRedirect(setting.AppSubURL + "/-/admin/packages?page=" + url.QueryEscape(ctx.FormString("page")) + "&q=" + url.QueryEscape(ctx.FormString("q")) + "&type=" + url.QueryEscape(ctx.FormString("type")))
} }
func CleanupExpiredData(ctx *context.Context) { func CleanupExpiredData(ctx *context.Context) {
@ -109,5 +109,5 @@ func CleanupExpiredData(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success")) ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success"))
ctx.Redirect(setting.AppSubURL + "/admin/packages") ctx.Redirect(setting.AppSubURL + "/-/admin/packages")
} }

View file

@ -53,7 +53,7 @@ func QueueSet(ctx *context.Context) {
maxNumber, err = strconv.Atoi(maxNumberStr) maxNumber, err = strconv.Atoi(maxNumberStr)
if err != nil { if err != nil {
ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.maxnumberworkers.error")) ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.maxnumberworkers.error"))
ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/monitor/queue/" + strconv.FormatInt(qid, 10))
return return
} }
if maxNumber < -1 { if maxNumber < -1 {
@ -65,7 +65,7 @@ func QueueSet(ctx *context.Context) {
mq.SetWorkerMaxNumber(maxNumber) mq.SetWorkerMaxNumber(maxNumber)
ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.changed")) ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.changed"))
ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/monitor/queue/" + strconv.FormatInt(qid, 10))
} }
func QueueRemoveAllItems(ctx *context.Context) { func QueueRemoveAllItems(ctx *context.Context) {
@ -85,5 +85,5 @@ func QueueRemoveAllItems(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.remove_all_items_done")) ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.remove_all_items_done"))
ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/monitor/queue/" + strconv.FormatInt(qid, 10))
} }

View file

@ -58,7 +58,7 @@ func DeleteRepo(ctx *context.Context) {
log.Trace("Repository deleted: %s", repo.FullName()) log.Trace("Repository deleted: %s", repo.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.JSONRedirect(setting.AppSubURL + "/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort"))) ctx.JSONRedirect(setting.AppSubURL + "/-/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort")))
} }
// UnadoptedRepos lists the unadopted repositories // UnadoptedRepos lists the unadopted repositories
@ -114,7 +114,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
dirSplit := strings.SplitN(dir, "/", 2) dirSplit := strings.SplitN(dir, "/", 2)
if len(dirSplit) != 2 { if len(dirSplit) != 2 {
ctx.Redirect(setting.AppSubURL + "/admin/repos") ctx.Redirect(setting.AppSubURL + "/-/admin/repos")
return return
} }
@ -122,7 +122,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
log.Debug("User does not exist: %s", dirSplit[0]) log.Debug("User does not exist: %s", dirSplit[0])
ctx.Redirect(setting.AppSubURL + "/admin/repos") ctx.Redirect(setting.AppSubURL + "/-/admin/repos")
return return
} }
ctx.ServerError("GetUserByName", err) ctx.ServerError("GetUserByName", err)
@ -160,5 +160,5 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir)) ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir))
} }
ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted?search=true&q=" + url.QueryEscape(q) + "&page=" + url.QueryEscape(page)) ctx.Redirect(setting.AppSubURL + "/-/admin/repos/unadopted?search=true&q=" + url.QueryEscape(q) + "&page=" + url.QueryEscape(page))
} }

View file

@ -9,5 +9,5 @@ import (
) )
func RedirectToDefaultSetting(ctx *context.Context) { func RedirectToDefaultSetting(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/actions/runners") ctx.Redirect(setting.AppSubURL + "/-/admin/actions/runners")
} }

View file

@ -42,5 +42,5 @@ func Stacktrace(ctx *context.Context) {
func StacktraceCancel(ctx *context.Context) { func StacktraceCancel(ctx *context.Context) {
pid := ctx.PathParam("pid") pid := ctx.PathParam("pid")
process.GetManager().Cancel(process.IDType(pid)) process.GetManager().Cancel(process.IDType(pid))
ctx.JSONRedirect(setting.AppSubURL + "/admin/monitor/stacktrace") ctx.JSONRedirect(setting.AppSubURL + "/-/admin/monitor/stacktrace")
} }

View file

@ -215,14 +215,14 @@ func NewUserPost(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name)) ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + strconv.FormatInt(u.ID, 10))
} }
func prepareUserInfo(ctx *context.Context) *user_model.User { func prepareUserInfo(ctx *context.Context) *user_model.User {
u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64(":userid")) u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64(":userid"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Redirect(setting.AppSubURL + "/admin/users") ctx.Redirect(setting.AppSubURL + "/-/admin/users")
} else { } else {
ctx.ServerError("GetUserByID", err) ctx.ServerError("GetUserByID", err)
} }
@ -481,7 +481,7 @@ func EditUserPost(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success")) ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
} }
// DeleteUser response for deleting a user // DeleteUser response for deleting a user
@ -495,7 +495,7 @@ func DeleteUser(ctx *context.Context) {
// admin should not delete themself // admin should not delete themself
if u.ID == ctx.Doer.ID { if u.ID == ctx.Doer.ID {
ctx.Flash.Error(ctx.Tr("admin.users.cannot_delete_self")) ctx.Flash.Error(ctx.Tr("admin.users.cannot_delete_self"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
return return
} }
@ -503,16 +503,16 @@ func DeleteUser(ctx *context.Context) {
switch { switch {
case models.IsErrUserOwnRepos(err): case models.IsErrUserOwnRepos(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
case models.IsErrUserHasOrgs(err): case models.IsErrUserHasOrgs(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_has_org")) ctx.Flash.Error(ctx.Tr("admin.users.still_has_org"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
case models.IsErrUserOwnPackages(err): case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages")) ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
case models.IsErrDeleteLastAdminUser(err): case models.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin")) ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
default: default:
ctx.ServerError("DeleteUser", err) ctx.ServerError("DeleteUser", err)
} }
@ -521,7 +521,7 @@ func DeleteUser(ctx *context.Context) {
log.Trace("Account deleted by admin (%s): %s", ctx.Doer.Name, u.Name) log.Trace("Account deleted by admin (%s): %s", ctx.Doer.Name, u.Name)
ctx.Flash.Success(ctx.Tr("admin.users.deletion_success")) ctx.Flash.Success(ctx.Tr("admin.users.deletion_success"))
ctx.Redirect(setting.AppSubURL + "/admin/users") ctx.Redirect(setting.AppSubURL + "/-/admin/users")
} }
// AvatarPost response for change user's avatar request // AvatarPost response for change user's avatar request
@ -538,7 +538,7 @@ func AvatarPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.update_user_avatar_success")) ctx.Flash.Success(ctx.Tr("settings.update_user_avatar_success"))
} }
ctx.Redirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + strconv.FormatInt(u.ID, 10))
} }
// DeleteAvatar render delete avatar page // DeleteAvatar render delete avatar page
@ -552,5 +552,5 @@ func DeleteAvatar(ctx *context.Context) {
ctx.Flash.Error(err.Error()) ctx.Flash.Error(err.Error())
} }
ctx.JSONRedirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)) ctx.JSONRedirect(setting.AppSubURL + "/-/admin/users/" + strconv.FormatInt(u.ID, 10))
} }

View file

@ -98,7 +98,7 @@ func autoSignIn(ctx *context.Context) (bool, error) {
return false, err return false, err
} }
ctx.Csrf.DeleteCookie(ctx) ctx.Csrf.PrepareForSessionUser(ctx)
return true, nil return true, nil
} }
@ -359,8 +359,8 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
} }
// Clear whatever CSRF cookie has right now, force to generate a new one // force to generate a new CSRF token
ctx.Csrf.DeleteCookie(ctx) ctx.Csrf.PrepareForSessionUser(ctx)
// Register last login // Register last login
if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil { if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
@ -804,6 +804,8 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
return return
} }
ctx.Csrf.PrepareForSessionUser(ctx)
if err := resetLocale(ctx, user); err != nil { if err := resetLocale(ctx, user); err != nil {
ctx.ServerError("resetLocale", err) ctx.ServerError("resetLocale", err)
return return

View file

@ -358,8 +358,8 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
return return
} }
// Clear whatever CSRF cookie has right now, force to generate a new one // force to generate a new CSRF token
ctx.Csrf.DeleteCookie(ctx) ctx.Csrf.PrepareForSessionUser(ctx)
if err := resetLocale(ctx, u); err != nil { if err := resetLocale(ctx, u); err != nil {
ctx.ServerError("resetLocale", err) ctx.ServerError("resetLocale", err)

View file

@ -166,7 +166,7 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["BaseTarget"] = pull.BaseBranch
headBranchLink := "" headBranchLink := ""
if pull.Flow == issues_model.PullRequestFlowGithub { if pull.Flow == issues_model.PullRequestFlowGithub {
b, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, pull.HeadBranch) b, err := git_model.GetBranch(ctx, pull.HeadRepoID, pull.HeadBranch)
switch { switch {
case err == nil: case err == nil:
if !b.IsDeleted { if !b.IsDeleted {
@ -887,8 +887,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
} }
if pull.HeadRepo != nil { if pull.HeadRepo != nil {
ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/commit/" + endCommitID
if !pull.HasMerged && ctx.Doer != nil { if !pull.HasMerged && ctx.Doer != nil {
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer) perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
if err != nil { if err != nil {

View file

@ -76,7 +76,7 @@ func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) {
IsAdmin: true, IsAdmin: true,
RunnersTemplate: tplAdminRunners, RunnersTemplate: tplAdminRunners,
RunnerEditTemplate: tplAdminRunnerEdit, RunnerEditTemplate: tplAdminRunnerEdit,
RedirectLink: setting.AppSubURL + "/admin/actions/runners/", RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/",
}, nil }, nil
} }

View file

@ -74,7 +74,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
RepoID: 0, RepoID: 0,
IsGlobal: true, IsGlobal: true,
VariablesTemplate: tplAdminVariables, VariablesTemplate: tplAdminVariables,
RedirectLink: setting.AppSubURL + "/admin/actions/variables", RedirectLink: setting.AppSubURL + "/-/admin/actions/variables",
}, nil }, nil
} }

View file

@ -100,8 +100,8 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
return &ownerRepoCtx{ return &ownerRepoCtx{
IsAdmin: true, IsAdmin: true,
IsSystemWebhook: ctx.PathParam(":configType") == "system-hooks", IsSystemWebhook: ctx.PathParam(":configType") == "system-hooks",
Link: path.Join(setting.AppSubURL, "/admin/hooks"), Link: path.Join(setting.AppSubURL, "/-/admin/hooks"),
LinkNew: path.Join(setting.AppSubURL, "/admin/", ctx.PathParam(":configType")), LinkNew: path.Join(setting.AppSubURL, "/-/admin/", ctx.PathParam(":configType")),
NewTemplate: tplAdminHookNew, NewTemplate: tplAdminHookNew,
}, nil }, nil
} }

View file

@ -683,7 +683,7 @@ func registerRoutes(m *web.Router) {
adminReq := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true, AdminRequired: true}) adminReq := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true, AdminRequired: true})
// ***** START: Admin ***** // ***** START: Admin *****
m.Group("/admin", func() { m.Group("/-/admin", func() {
m.Get("", admin.Dashboard) m.Get("", admin.Dashboard)
m.Get("/system_status", admin.SystemStatus) m.Get("/system_status", admin.SystemStatus)
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost) m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
@ -1461,6 +1461,35 @@ func registerRoutes(m *web.Router) {
) )
// end "/{username}/{reponame}/activity" // end "/{username}/{reponame}/activity"
m.Group("/{username}/{reponame}", func() {
m.Group("/pulls/{index}", func() {
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
m.Get(".diff", repo.DownloadPullDiff)
m.Get(".patch", repo.DownloadPullPatch)
m.Group("/commits", func() {
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
})
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
m.Post("/update", repo.UpdatePullRequest)
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
m.Group("/files", func() {
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
m.Group("/reviews", func() {
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
}, context.RepoMustNotBeArchived())
})
})
}, ignSignIn, context.RepoAssignment, repo.MustAllowPulls, reqRepoPullsReader)
// end "/{username}/{reponame}/pulls/{index}": repo pull request
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Group("/activity_author_data", func() { m.Group("/activity_author_data", func() {
m.Get("", repo.ActivityAuthors) m.Get("", repo.ActivityAuthors)
@ -1499,32 +1528,6 @@ func registerRoutes(m *web.Router) {
return cancel return cancel
}) })
m.Group("/pulls/{index}", func() {
m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
m.Get(".diff", repo.DownloadPullDiff)
m.Get(".patch", repo.DownloadPullPatch)
m.Group("/commits", func() {
m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits)
m.Get("/list", context.RepoRef(), repo.GetPullCommits)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit)
})
m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest)
m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest)
m.Post("/update", repo.UpdatePullRequest)
m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits)
m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
m.Group("/files", func() {
m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr)
m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit)
m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange)
m.Group("/reviews", func() {
m.Get("/new_comment", repo.RenderNewCodeCommentForm)
m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment)
m.Post("/submit", web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview)
}, context.RepoMustNotBeArchived())
})
}, repo.MustAllowPulls)
m.Group("/media", func() { m.Group("/media", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS) m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS) m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)

View file

@ -116,11 +116,20 @@ func (input *notifyInput) Notify(ctx context.Context) {
} }
func notify(ctx context.Context, input *notifyInput) error { func notify(ctx context.Context, input *notifyInput) error {
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
if input.Doer.IsActions() { if input.Doer.IsActions() {
// avoiding triggering cyclically, for example: // avoiding triggering cyclically, for example:
// a comment of an issue will trigger the runner to add a new comment as reply, // a comment of an issue will trigger the runner to add a new comment as reply,
// and the new comment will trigger the runner again. // and the new comment will trigger the runner again.
log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name) log.Debug("ignore executing %v for event %v whose doer is %v", getMethod(ctx), input.Event, input.Doer.Name)
// we should update schedule tasks in this case, because
// 1. schedule tasks cannot be triggered by other events, so cyclic triggering will not occur
// 2. some schedule tasks may update the repo periodically, so the refs of schedule tasks need to be updated
if shouldDetectSchedules {
return DetectAndHandleSchedules(ctx, input.Repo)
}
return nil return nil
} }
if input.Repo.IsEmpty || input.Repo.IsArchived { if input.Repo.IsEmpty || input.Repo.IsArchived {
@ -174,7 +183,6 @@ func notify(ctx context.Context, input *notifyInput) error {
var detectedWorkflows []*actions_module.DetectedWorkflow var detectedWorkflows []*actions_module.DetectedWorkflow
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig() actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
input.Event, input.Event,
input.Payload, input.Payload,

View file

@ -7,7 +7,6 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
@ -24,10 +23,10 @@ import (
// ProcReceive handle proc receive work // ProcReceive handle proc receive work
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) { func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs)) results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
topicBranch := opts.GitPushOptions["topic"] topicBranch := opts.GitPushOptions["topic"]
forcePush, _ := strconv.ParseBool(opts.GitPushOptions["force-push"])
title := strings.TrimSpace(opts.GitPushOptions["title"]) title := strings.TrimSpace(opts.GitPushOptions["title"])
description := strings.TrimSpace(opts.GitPushOptions["description"]) // TODO: Add more options? description := strings.TrimSpace(opts.GitPushOptions["description"])
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
userName := strings.ToLower(opts.UserName) userName := strings.ToLower(opts.UserName)
@ -56,19 +55,19 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
} }
baseBranchName := opts.RefFullNames[i].ForBranchName() baseBranchName := opts.RefFullNames[i].ForBranchName()
curentTopicBranch := "" currentTopicBranch := ""
if !gitRepo.IsBranchExist(baseBranchName) { if !gitRepo.IsBranchExist(baseBranchName) {
// try match refs/for/<target-branch>/<topic-branch> // try match refs/for/<target-branch>/<topic-branch>
for p, v := range baseBranchName { for p, v := range baseBranchName {
if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 { if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
curentTopicBranch = baseBranchName[p+1:] currentTopicBranch = baseBranchName[p+1:]
baseBranchName = baseBranchName[:p] baseBranchName = baseBranchName[:p]
break break
} }
} }
} }
if len(topicBranch) == 0 && len(curentTopicBranch) == 0 { if len(topicBranch) == 0 && len(currentTopicBranch) == 0 {
results = append(results, private.HookProcReceiveRefResult{ results = append(results, private.HookProcReceiveRefResult{
OriginalRef: opts.RefFullNames[i], OriginalRef: opts.RefFullNames[i],
OldOID: opts.OldCommitIDs[i], OldOID: opts.OldCommitIDs[i],
@ -78,18 +77,18 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
continue continue
} }
if len(curentTopicBranch) == 0 { if len(currentTopicBranch) == 0 {
curentTopicBranch = topicBranch currentTopicBranch = topicBranch
} }
// because different user maybe want to use same topic, // because different user maybe want to use same topic,
// So it's better to make sure the topic branch name // So it's better to make sure the topic branch name
// has username prefix // has username prefix
var headBranch string var headBranch string
if !strings.HasPrefix(curentTopicBranch, userName+"/") { if !strings.HasPrefix(currentTopicBranch, userName+"/") {
headBranch = userName + "/" + curentTopicBranch headBranch = userName + "/" + currentTopicBranch
} else { } else {
headBranch = curentTopicBranch headBranch = currentTopicBranch
} }
pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, repo.ID, headBranch, baseBranchName, issues_model.PullRequestFlowAGit) pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, repo.ID, headBranch, baseBranchName, issues_model.PullRequestFlowAGit)
@ -178,7 +177,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
continue continue
} }
if !forcePush { if !forcePush.Value() {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1"). output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").
AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]). AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]).
RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()}) RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()})

View file

@ -103,8 +103,8 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
middleware.SetLocaleCookie(resp, user.Language, 0) middleware.SetLocaleCookie(resp, user.Language, 0)
// Clear whatever CSRF has right now, force to generate a new one // force to generate a new CSRF token
if ctx := gitea_context.GetWebContext(req); ctx != nil { if ctx := gitea_context.GetWebContext(req); ctx != nil {
ctx.Csrf.DeleteCookie(ctx) ctx.Csrf.PrepareForSessionUser(ctx)
} }
} }

View file

@ -38,6 +38,7 @@ type APIContext struct {
Repo *Repository Repo *Repository
Org *APIOrganization Org *APIOrganization
Package *Package Package *Package
PublicOnly bool // Whether the request is for a public endpoint
} }
func init() { func init() {

View file

@ -129,10 +129,8 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
} }
if needsNew { if needsNew {
// FIXME: actionId.
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now()) c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
cookie := newCsrfCookie(&c.opt, c.token) ctx.Resp.Header().Add("Set-Cookie", newCsrfCookie(&c.opt, c.token).String())
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
} }
ctx.Data["CsrfToken"] = c.token ctx.Data["CsrfToken"] = c.token

View file

@ -58,6 +58,9 @@ func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) {
func RequireRepoReader(unitType unit.Type) func(ctx *Context) { func RequireRepoReader(unitType unit.Type) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.CanRead(unitType) { if !ctx.Repo.CanRead(unitType) {
if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
return
}
if log.IsTrace() { if log.IsTrace() {
if ctx.IsSigned { if ctx.IsSigned {
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+ log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+

View file

@ -374,7 +374,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
return return
} }
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() { if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) {
if ctx.FormString("go-get") == "1" { if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx) EarlyResponseForGoGetMeta(ctx)
return return
@ -1058,3 +1058,11 @@ func GitHookService() func(ctx *Context) {
} }
} }
} }
// canWriteAsMaintainer check if the doer can write to a branch as a maintainer
func canWriteAsMaintainer(ctx *Context) bool {
branchName := getRefNameFromPath(ctx.Repo, ctx.PathParam("*"), func(branchName string) bool {
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.Repo.Permission, branchName, ctx.Doer)
})
return len(branchName) > 0
}

View file

@ -760,10 +760,15 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model
pr.Updated = pr.Created pr.Updated = pr.Created
} }
prTitle := pr.Title
if pr.IsDraft && !issues_model.HasWorkInProgressPrefix(pr.Title) {
prTitle = fmt.Sprintf("%s %s", setting.Repository.PullRequest.WorkInProgressPrefixes[0], pr.Title)
}
issue := issues_model.Issue{ issue := issues_model.Issue{
RepoID: g.repo.ID, RepoID: g.repo.ID,
Repo: g.repo, Repo: g.repo,
Title: pr.Title, Title: prTitle,
Index: pr.Number, Index: pr.Number,
Content: pr.Content, Content: pr.Content,
MilestoneID: milestoneID, MilestoneID: milestoneID,

View file

@ -737,6 +737,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
PatchURL: pr.GetPatchURL(), // see below for SECURITY related issues here PatchURL: pr.GetPatchURL(), // see below for SECURITY related issues here
Reactions: reactions, Reactions: reactions,
ForeignIndex: int64(*pr.Number), ForeignIndex: int64(*pr.Number),
IsDraft: pr.GetDraft(),
}) })
// SECURITY: Ensure that the PR is safe // SECURITY: Ensure that the PR is safe

View file

@ -722,6 +722,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
PatchURL: pr.WebURL + ".patch", PatchURL: pr.WebURL + ".patch",
ForeignIndex: int64(pr.IID), ForeignIndex: int64(pr.IID),
Context: gitlabIssueContext{IsMergeRequest: true}, Context: gitlabIssueContext{IsMergeRequest: true},
IsDraft: pr.Draft,
}) })
// SECURITY: Ensure that the PR is safe // SECURITY: Ensure that the PR is safe

View file

@ -32,6 +32,10 @@ import (
// RenameUser renames a user // RenameUser renames a user
func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
if newUserName == u.Name {
return nil
}
// Non-local users are not allowed to change their username. // Non-local users are not allowed to change their username.
if !u.IsOrganization() && !u.IsLocal() { if !u.IsOrganization() && !u.IsLocal() {
return user_model.ErrUserIsNotLocal{ return user_model.ErrUserIsNotLocal{
@ -40,10 +44,6 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string) err
} }
} }
if newUserName == u.Name {
return nil
}
if err := user_model.IsUsableUsername(newUserName); err != nil { if err := user_model.IsUsableUsername(newUserName); err != nil {
return err return err
} }

View file

@ -114,12 +114,10 @@ func TestRenameUser(t *testing.T) {
}) })
t.Run("Non usable username", func(t *testing.T) { t.Run("Non usable username", func(t *testing.T) {
usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"} usernames := []string{"--diff", ".well-known", "gitea-actions", "aaa.atom", "aa.png"}
for _, username := range usernames { for _, username := range usernames {
t.Run(username, func(t *testing.T) { assert.Error(t, user_model.IsUsableUsername(username), "non-usable username: %s", username)
assert.Error(t, user_model.IsUsableUsername(username)) assert.Error(t, RenameUser(db.DefaultContext, user, username), "non-usable username: %s", username)
assert.Error(t, RenameUser(db.DefaultContext, user, username))
})
} }
}) })

View file

@ -59,7 +59,7 @@ func (m *webhookNotifier) IssueClearLabels(ctx context.Context, doer *user_model
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelCleared, Action: api.HookIssueLabelCleared,
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -150,7 +150,7 @@ func (m *webhookNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
} }
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
} }
@ -201,7 +201,7 @@ func (m *webhookNotifier) IssueChangeTitle(ctx context.Context, doer *user_model
From: oldTitle, From: oldTitle,
}, },
}, },
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -236,7 +236,7 @@ func (m *webhookNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
// Merge pull request calls issue.changeStatus so we need to handle separately. // Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
CommitID: commitID, CommitID: commitID,
@ -307,7 +307,7 @@ func (m *webhookNotifier) NewPullRequest(ctx context.Context, pull *issues_model
if err := PrepareWebhooks(ctx, EventSource{Repository: pull.Issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: pull.Issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{
Action: api.HookIssueOpened, Action: api.HookIssueOpened,
Index: pull.Issue.Index, Index: pull.Issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, pull, nil), PullRequest: convert.ToAPIPullRequest(ctx, pull, pull.Issue.Poster),
Repository: convert.ToRepo(ctx, pull.Issue.Repo, permission), Repository: convert.ToRepo(ctx, pull.Issue.Repo, permission),
Sender: convert.ToUser(ctx, pull.Issue.Poster, nil), Sender: convert.ToUser(ctx, pull.Issue.Poster, nil),
}); err != nil { }); err != nil {
@ -336,7 +336,7 @@ func (m *webhookNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
From: oldContent, From: oldContent,
}, },
}, },
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -375,8 +375,10 @@ func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
} }
var eventType webhook_module.HookEventType var eventType webhook_module.HookEventType
var pullRequest *api.PullRequest
if c.Issue.IsPull { if c.Issue.IsPull {
eventType = webhook_module.HookEventPullRequestComment eventType = webhook_module.HookEventPullRequestComment
pullRequest = convert.ToAPIPullRequest(ctx, c.Issue.PullRequest, doer)
} else { } else {
eventType = webhook_module.HookEventIssueComment eventType = webhook_module.HookEventIssueComment
} }
@ -385,6 +387,7 @@ func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited, Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(ctx, doer, c.Issue), Issue: convert.ToAPIIssue(ctx, doer, c.Issue),
PullRequest: pullRequest,
Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c), Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
Changes: &api.ChangesPayload{ Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{ Body: &api.ChangesFromPayload{
@ -403,8 +406,10 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User, issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
) { ) {
var eventType webhook_module.HookEventType var eventType webhook_module.HookEventType
var pullRequest *api.PullRequest
if issue.IsPull { if issue.IsPull {
eventType = webhook_module.HookEventPullRequestComment eventType = webhook_module.HookEventPullRequestComment
pullRequest = convert.ToAPIPullRequest(ctx, issue.PullRequest, doer)
} else { } else {
eventType = webhook_module.HookEventIssueComment eventType = webhook_module.HookEventIssueComment
} }
@ -413,6 +418,7 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated, Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, doer, issue), Issue: convert.ToAPIIssue(ctx, doer, issue),
PullRequest: pullRequest,
Comment: convert.ToAPIComment(ctx, repo, comment), Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission), Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
@ -440,8 +446,10 @@ func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
} }
var eventType webhook_module.HookEventType var eventType webhook_module.HookEventType
var pullRequest *api.PullRequest
if comment.Issue.IsPull { if comment.Issue.IsPull {
eventType = webhook_module.HookEventPullRequestComment eventType = webhook_module.HookEventPullRequestComment
pullRequest = convert.ToAPIPullRequest(ctx, comment.Issue.PullRequest, doer)
} else { } else {
eventType = webhook_module.HookEventIssueComment eventType = webhook_module.HookEventIssueComment
} }
@ -450,6 +458,7 @@ func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted, Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(ctx, doer, comment.Issue), Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
PullRequest: pullRequest,
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment), Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission), Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
@ -525,7 +534,7 @@ func (m *webhookNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestLabel, &api.PullRequestPayload{
Action: api.HookIssueLabelUpdated, Action: api.HookIssueLabelUpdated,
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}), Repository: convert.ToRepo(ctx, issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -567,7 +576,7 @@ func (m *webhookNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestMilestone, &api.PullRequestPayload{ err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequestMilestone, &api.PullRequestPayload{
Action: hookAction, Action: hookAction,
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}) })
@ -640,7 +649,7 @@ func (*webhookNotifier) MergePullRequest(ctx context.Context, doer *user_model.U
// Merge pull request calls issue.changeStatus so we need to handle separately. // Merge pull request calls issue.changeStatus so we need to handle separately.
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: pr.Issue.Index, Index: pr.Issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), PullRequest: convert.ToAPIPullRequest(ctx, pr, doer),
Repository: convert.ToRepo(ctx, pr.Issue.Repo, permission), Repository: convert.ToRepo(ctx, pr.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
Action: api.HookIssueClosed, Action: api.HookIssueClosed,
@ -668,7 +677,7 @@ func (m *webhookNotifier) PullRequestChangeTargetBranch(ctx context.Context, doe
From: oldBranch, From: oldBranch,
}, },
}, },
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), PullRequest: convert.ToAPIPullRequest(ctx, pr, doer),
Repository: convert.ToRepo(ctx, issue.Repo, mode), Repository: convert.ToRepo(ctx, issue.Repo, mode),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}); err != nil { }); err != nil {
@ -705,7 +714,8 @@ func (m *webhookNotifier) PullRequestReview(ctx context.Context, pr *issues_mode
if err := PrepareWebhooks(ctx, EventSource{Repository: review.Issue.Repo}, reviewHookType, &api.PullRequestPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: review.Issue.Repo}, reviewHookType, &api.PullRequestPayload{
Action: api.HookIssueReviewed, Action: api.HookIssueReviewed,
Index: review.Issue.Index, Index: review.Issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), PullRequest: convert.ToAPIPullRequest(ctx, pr, review.Reviewer),
RequestedReviewer: convert.ToUser(ctx, review.Reviewer, nil),
Repository: convert.ToRepo(ctx, review.Issue.Repo, permission), Repository: convert.ToRepo(ctx, review.Issue.Repo, permission),
Sender: convert.ToUser(ctx, review.Reviewer, nil), Sender: convert.ToUser(ctx, review.Reviewer, nil),
Review: &api.ReviewPayload{ Review: &api.ReviewPayload{
@ -729,7 +739,7 @@ func (m *webhookNotifier) PullRequestReviewRequest(ctx context.Context, doer *us
} }
apiPullRequest := &api.PullRequestPayload{ apiPullRequest := &api.PullRequestPayload{
Index: issue.Index, Index: issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil), PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
RequestedReviewer: convert.ToUser(ctx, reviewer, nil), RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
Repository: convert.ToRepo(ctx, issue.Repo, permission), Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
@ -774,7 +784,7 @@ func (m *webhookNotifier) PullRequestSynchronized(ctx context.Context, doer *use
if err := PrepareWebhooks(ctx, EventSource{Repository: pr.Issue.Repo}, webhook_module.HookEventPullRequestSync, &api.PullRequestPayload{ if err := PrepareWebhooks(ctx, EventSource{Repository: pr.Issue.Repo}, webhook_module.HookEventPullRequestSync, &api.PullRequestPayload{
Action: api.HookIssueSynchronized, Action: api.HookIssueSynchronized,
Index: pr.Issue.Index, Index: pr.Issue.Index,
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil), PullRequest: convert.ToAPIPullRequest(ctx, pr, doer),
Repository: convert.ToRepo(ctx, pr.Issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}), Repository: convert.ToRepo(ctx, pr.Issue.Repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
Sender: convert.ToUser(ctx, doer, nil), Sender: convert.ToUser(ctx, doer, nil),
}); err != nil { }); err != nil {

View file

@ -3,7 +3,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.auths.auth_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.auths.auth_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/auths/new">{{ctx.Locale.Tr "admin.auths.new"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/-/admin/auths/new">{{ctx.Locale.Tr "admin.auths.new"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
@ -23,12 +23,12 @@
{{range .Sources}} {{range .Sources}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{.Name}}</a></td> <td><a href="{{AppSubUrl}}/-/admin/auths/{{.ID}}">{{.Name}}</a></td>
<td>{{.TypeName}}</td> <td>{{.TypeName}}</td>
<td>{{svg (Iif .IsActive "octicon-check" "octicon-x")}}</td> <td>{{svg (Iif .IsActive "octicon-check" "octicon-x")}}</td>
<td>{{DateTime "short" .UpdatedUnix}}</td> <td>{{DateTime "short" .UpdatedUnix}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix}}</td>
<td><a href="{{AppSubUrl}}/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td> <td><a href="{{AppSubUrl}}/-/admin/auths/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View file

@ -231,7 +231,7 @@
<div class="divider"></div> <div class="divider"></div>
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt> <dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
<dd class="tw-py-0"> <dd class="tw-py-0">
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/-/admin/config/test_mail" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="ui tiny input"> <div class="ui tiny input">
<input type="email" name="email" placeholder="{{ctx.Locale.Tr "admin.config.test_email_placeholder"}}" size="29" required> <input type="email" name="email" placeholder="{{ctx.Locale.Tr "admin.config.test_email_placeholder"}}" size="29" required>
@ -263,7 +263,7 @@
<div class="divider"></div> <div class="divider"></div>
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.cache_test"}}</dt> <dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.cache_test"}}</dt>
<dd class="tw-py-0"> <dd class="tw-py-0">
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_cache" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/-/admin/config/test_cache" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button class="ui tiny primary button">{{ctx.Locale.Tr "test"}}</button> <button class="ui tiny primary button">{{ctx.Locale.Tr "test"}}</button>
</form> </form>

View file

@ -24,7 +24,7 @@
{{ctx.Locale.Tr "repository"}} {{ctx.Locale.Tr "repository"}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form form-fetch-action" method="post" action="{{AppSubUrl}}/admin/config?key={{.SystemConfig.Repository.OpenWithEditorApps.DynKey}}"> <form class="ui form form-fetch-action" method="post" action="{{AppSubUrl}}/-/admin/config?key={{.SystemConfig.Repository.OpenWithEditorApps.DynKey}}">
<div class="field"> <div class="field">
<details> <details>
<summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary> <summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary>

View file

@ -4,7 +4,7 @@
{{ctx.Locale.Tr "admin.monitor.cron"}} {{ctx.Locale.Tr "admin.monitor.cron"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/-/admin">
<table class="ui very basic striped table unstackable tw-mb-0"> <table class="ui very basic striped table unstackable tw-mb-0">
<thead> <thead>
<tr> <tr>

View file

@ -9,7 +9,7 @@
{{ctx.Locale.Tr "admin.dashboard.maintenance_operations"}} {{ctx.Locale.Tr "admin.dashboard.maintenance_operations"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/-/admin">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<table class="ui very basic table tw-mt-0 tw-px-4"> <table class="ui very basic table tw-mt-0 tw-px-4">
<tbody> <tbody>

View file

@ -80,7 +80,7 @@
<div class="content"> <div class="content">
<p class="center">{{ctx.Locale.Tr "admin.emails.change_email_text"}}</p> <p class="center">{{ctx.Locale.Tr "admin.emails.change_email_text"}}</p>
<form class="ui form" id="email-action-form" action="{{AppSubUrl}}/admin/emails/activate" method="post"> <form class="ui form" id="email-action-form" action="{{AppSubUrl}}/-/admin/emails/activate" method="post">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<input type="hidden" id="query-sort" name="sort" value="{{.SortType}}"> <input type="hidden" id="query-sort" name="sort" value="{{.SortType}}">

View file

@ -5,10 +5,10 @@
<details class="item toggleable-item" {{if or .PageIsAdminDashboard .PageIsAdminSelfCheck}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsAdminDashboard .PageIsAdminSelfCheck}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.maintenance"}}</summary> <summary>{{ctx.Locale.Tr "admin.maintenance"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsAdminDashboard}}active {{end}}item" href="{{AppSubUrl}}/admin"> <a class="{{if .PageIsAdminDashboard}}active {{end}}item" href="{{AppSubUrl}}/-/admin">
{{ctx.Locale.Tr "admin.dashboard"}} {{ctx.Locale.Tr "admin.dashboard"}}
</a> </a>
<a class="{{if .PageIsAdminSelfCheck}}active {{end}}item" href="{{AppSubUrl}}/admin/self_check"> <a class="{{if .PageIsAdminSelfCheck}}active {{end}}item" href="{{AppSubUrl}}/-/admin/self_check">
{{ctx.Locale.Tr "admin.self_check"}} {{ctx.Locale.Tr "admin.self_check"}}
</a> </a>
</div> </div>
@ -16,16 +16,16 @@
<details class="item toggleable-item" {{if or .PageIsAdminUsers .PageIsAdminEmails .PageIsAdminOrganizations .PageIsAdminAuthentications}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsAdminUsers .PageIsAdminEmails .PageIsAdminOrganizations .PageIsAdminAuthentications}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.identity_access"}}</summary> <summary>{{ctx.Locale.Tr "admin.identity_access"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsAdminAuthentications}}active {{end}}item" href="{{AppSubUrl}}/admin/auths"> <a class="{{if .PageIsAdminAuthentications}}active {{end}}item" href="{{AppSubUrl}}/-/admin/auths">
{{ctx.Locale.Tr "admin.authentication"}} {{ctx.Locale.Tr "admin.authentication"}}
</a> </a>
<a class="{{if .PageIsAdminOrganizations}}active {{end}}item" href="{{AppSubUrl}}/admin/orgs"> <a class="{{if .PageIsAdminOrganizations}}active {{end}}item" href="{{AppSubUrl}}/-/admin/orgs">
{{ctx.Locale.Tr "admin.organizations"}} {{ctx.Locale.Tr "admin.organizations"}}
</a> </a>
<a class="{{if .PageIsAdminUsers}}active {{end}}item" href="{{AppSubUrl}}/admin/users"> <a class="{{if .PageIsAdminUsers}}active {{end}}item" href="{{AppSubUrl}}/-/admin/users">
{{ctx.Locale.Tr "admin.users"}} {{ctx.Locale.Tr "admin.users"}}
</a> </a>
<a class="{{if .PageIsAdminEmails}}active {{end}}item" href="{{AppSubUrl}}/admin/emails"> <a class="{{if .PageIsAdminEmails}}active {{end}}item" href="{{AppSubUrl}}/-/admin/emails">
{{ctx.Locale.Tr "admin.emails"}} {{ctx.Locale.Tr "admin.emails"}}
</a> </a>
</div> </div>
@ -34,11 +34,11 @@
<summary>{{ctx.Locale.Tr "admin.assets"}}</summary> <summary>{{ctx.Locale.Tr "admin.assets"}}</summary>
<div class="menu"> <div class="menu">
{{if .EnablePackages}} {{if .EnablePackages}}
<a class="{{if .PageIsAdminPackages}}active {{end}}item" href="{{AppSubUrl}}/admin/packages"> <a class="{{if .PageIsAdminPackages}}active {{end}}item" href="{{AppSubUrl}}/-/admin/packages">
{{ctx.Locale.Tr "packages.title"}} {{ctx.Locale.Tr "packages.title"}}
</a> </a>
{{end}} {{end}}
<a class="{{if .PageIsAdminRepositories}}active {{end}}item" href="{{AppSubUrl}}/admin/repos"> <a class="{{if .PageIsAdminRepositories}}active {{end}}item" href="{{AppSubUrl}}/-/admin/repos">
{{ctx.Locale.Tr "admin.repositories"}} {{ctx.Locale.Tr "admin.repositories"}}
</a> </a>
</div> </div>
@ -48,22 +48,22 @@
<details class="item toggleable-item" {{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks .PageIsAdminApplications}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks .PageIsAdminApplications}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.integrations"}}</summary> <summary>{{ctx.Locale.Tr "admin.integrations"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsAdminApplications}}active {{end}}item" href="{{AppSubUrl}}/admin/applications"> <a class="{{if .PageIsAdminApplications}}active {{end}}item" href="{{AppSubUrl}}/-/admin/applications">
{{ctx.Locale.Tr "settings.applications"}} {{ctx.Locale.Tr "settings.applications"}}
</a> </a>
<a class="{{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks}}active {{end}}item" href="{{AppSubUrl}}/admin/hooks"> <a class="{{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks}}active {{end}}item" href="{{AppSubUrl}}/-/admin/hooks">
{{ctx.Locale.Tr "admin.hooks"}} {{ctx.Locale.Tr "admin.hooks"}}
</a> </a>
</div> </div>
</details> </details>
{{else}} {{else}}
{{if not DisableWebhooks}} {{if not DisableWebhooks}}
<a class="{{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks}}active {{end}}item" href="{{AppSubUrl}}/admin/hooks"> <a class="{{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks}}active {{end}}item" href="{{AppSubUrl}}/-/admin/hooks">
{{ctx.Locale.Tr "admin.hooks"}} {{ctx.Locale.Tr "admin.hooks"}}
</a> </a>
{{end}} {{end}}
{{if .EnableOAuth2}} {{if .EnableOAuth2}}
<a class="{{if .PageIsAdminApplications}}active {{end}}item" href="{{AppSubUrl}}/admin/applications"> <a class="{{if .PageIsAdminApplications}}active {{end}}item" href="{{AppSubUrl}}/-/admin/applications">
{{ctx.Locale.Tr "settings.applications"}} {{ctx.Locale.Tr "settings.applications"}}
</a> </a>
{{end}} {{end}}
@ -72,10 +72,10 @@
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsVariables}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsVariables}}open{{end}}>
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary> <summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{AppSubUrl}}/admin/actions/runners"> <a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{AppSubUrl}}/-/admin/actions/runners">
{{ctx.Locale.Tr "actions.runners"}} {{ctx.Locale.Tr "actions.runners"}}
</a> </a>
<a class="{{if .PageIsSharedSettingsVariables}}active {{end}}item" href="{{AppSubUrl}}/admin/actions/variables"> <a class="{{if .PageIsSharedSettingsVariables}}active {{end}}item" href="{{AppSubUrl}}/-/admin/actions/variables">
{{ctx.Locale.Tr "actions.variables"}} {{ctx.Locale.Tr "actions.variables"}}
</a> </a>
</div> </div>
@ -84,30 +84,30 @@
<details class="item toggleable-item" {{if or .PageIsAdminConfig}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsAdminConfig}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.config"}}</summary> <summary>{{ctx.Locale.Tr "admin.config"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsAdminConfigSummary}}active {{end}}item" href="{{AppSubUrl}}/admin/config"> <a class="{{if .PageIsAdminConfigSummary}}active {{end}}item" href="{{AppSubUrl}}/-/admin/config">
{{ctx.Locale.Tr "admin.config_summary"}} {{ctx.Locale.Tr "admin.config_summary"}}
</a> </a>
<a class="{{if .PageIsAdminConfigSettings}}active {{end}}item" href="{{AppSubUrl}}/admin/config/settings"> <a class="{{if .PageIsAdminConfigSettings}}active {{end}}item" href="{{AppSubUrl}}/-/admin/config/settings">
{{ctx.Locale.Tr "admin.config_settings"}} {{ctx.Locale.Tr "admin.config_settings"}}
</a> </a>
</div> </div>
</details> </details>
<a class="{{if .PageIsAdminNotices}}active {{end}}item" href="{{AppSubUrl}}/admin/notices"> <a class="{{if .PageIsAdminNotices}}active {{end}}item" href="{{AppSubUrl}}/-/admin/notices">
{{ctx.Locale.Tr "admin.notices"}} {{ctx.Locale.Tr "admin.notices"}}
</a> </a>
<details class="item toggleable-item" {{if or .PageIsAdminMonitorStats .PageIsAdminMonitorCron .PageIsAdminMonitorQueue .PageIsAdminMonitorStacktrace}}open{{end}}> <details class="item toggleable-item" {{if or .PageIsAdminMonitorStats .PageIsAdminMonitorCron .PageIsAdminMonitorQueue .PageIsAdminMonitorStacktrace}}open{{end}}>
<summary>{{ctx.Locale.Tr "admin.monitor"}}</summary> <summary>{{ctx.Locale.Tr "admin.monitor"}}</summary>
<div class="menu"> <div class="menu">
<a class="{{if .PageIsAdminMonitorStats}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/stats"> <a class="{{if .PageIsAdminMonitorStats}}active {{end}}item" href="{{AppSubUrl}}/-/admin/monitor/stats">
{{ctx.Locale.Tr "admin.monitor.stats"}} {{ctx.Locale.Tr "admin.monitor.stats"}}
</a> </a>
<a class="{{if .PageIsAdminMonitorCron}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/cron"> <a class="{{if .PageIsAdminMonitorCron}}active {{end}}item" href="{{AppSubUrl}}/-/admin/monitor/cron">
{{ctx.Locale.Tr "admin.monitor.cron"}} {{ctx.Locale.Tr "admin.monitor.cron"}}
</a> </a>
<a class="{{if .PageIsAdminMonitorQueue}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/queue"> <a class="{{if .PageIsAdminMonitorQueue}}active {{end}}item" href="{{AppSubUrl}}/-/admin/monitor/queue">
{{ctx.Locale.Tr "admin.monitor.queues"}} {{ctx.Locale.Tr "admin.monitor.queues"}}
</a> </a>
<a class="{{if .PageIsAdminMonitorStacktrace}}active {{end}}item" href="{{AppSubUrl}}/admin/monitor/stacktrace"> <a class="{{if .PageIsAdminMonitorStacktrace}}active {{end}}item" href="{{AppSubUrl}}/-/admin/monitor/stacktrace">
{{ctx.Locale.Tr "admin.monitor.stacktrace"}} {{ctx.Locale.Tr "admin.monitor.stacktrace"}}
</a> </a>
</div> </div>

View file

@ -31,7 +31,7 @@
<tr> <tr>
<th></th> <th></th>
<th colspan="5"> <th colspan="5">
<form class="tw-float-right" method="post" action="{{AppSubUrl}}/admin/notices/empty"> <form class="tw-float-right" method="post" action="{{AppSubUrl}}/-/admin/notices/empty">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button type="submit" class="ui red small button">{{ctx.Locale.Tr "admin.notices.delete_all"}}</button> <button type="submit" class="ui red small button">{{ctx.Locale.Tr "admin.notices.delete_all"}}</button>
</form> </form>

View file

@ -5,7 +5,7 @@
{{ctx.Locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}}, {{ctx.Locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}},
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}}) {{ctx.Locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}})
<div class="ui right"> <div class="ui right">
<form method="post" action="{{AppSubUrl}}/admin/packages/cleanup"> <form method="post" action="{{AppSubUrl}}/-/admin/packages/cleanup">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button> <button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button>
</form> </form>

View file

@ -3,7 +3,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.repos.repo_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.repos.repo_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/repos/unadopted">{{ctx.Locale.Tr "admin.repos.unadopted"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/-/admin/repos/unadopted">{{ctx.Locale.Tr "admin.repos.unadopted"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">

View file

@ -3,7 +3,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.repos.unadopted"}} {{ctx.Locale.Tr "admin.repos.unadopted"}}
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/repos">{{ctx.Locale.Tr "admin.repos.repo_manage_panel"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/-/admin/repos">{{ctx.Locale.Tr "admin.repos.repo_manage_panel"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
@ -31,7 +31,7 @@
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "repo.adopt_preexisting_content" $dir}}</p> <p>{{ctx.Locale.Tr "repo.adopt_preexisting_content" $dir}}</p>
</div> </div>
<form class="ui form" method="post" action="{{AppSubUrl}}/admin/repos/unadopted"> <form class="ui form" method="post" action="{{AppSubUrl}}/-/admin/repos/unadopted">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{$dir}}"> <input type="hidden" name="id" value="{{$dir}}">
<input type="hidden" name="action" value="adopt"> <input type="hidden" name="action" value="adopt">
@ -48,7 +48,7 @@
<div class="content"> <div class="content">
<p>{{ctx.Locale.Tr "repo.delete_preexisting_content" $dir}}</p> <p>{{ctx.Locale.Tr "repo.delete_preexisting_content" $dir}}</p>
</div> </div>
<form class="ui form" method="post" action="{{AppSubUrl}}/admin/repos/unadopted"> <form class="ui form" method="post" action="{{AppSubUrl}}/-/admin/repos/unadopted">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{$dir}}"> <input type="hidden" name="id" value="{{$dir}}">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">

View file

@ -8,7 +8,7 @@
<a class="{{if eq .ShowGoroutineList "stacktrace"}}active {{end}}item" href="?show=stacktrace">{{ctx.Locale.Tr "admin.monitor.stacktrace"}}</a> <a class="{{if eq .ShowGoroutineList "stacktrace"}}active {{end}}item" href="?show=stacktrace">{{ctx.Locale.Tr "admin.monitor.stacktrace"}}</a>
</div> </div>
</div> </div>
<form target="_blank" action="{{AppSubUrl}}/admin/monitor/diagnosis" class="ui form"> <form target="_blank" action="{{AppSubUrl}}/-/admin/monitor/diagnosis" class="ui form">
<div class="ui inline field"> <div class="ui inline field">
<button class="ui primary small button">{{ctx.Locale.Tr "admin.monitor.download_diagnosis_report"}}</button> <button class="ui primary small button">{{ctx.Locale.Tr "admin.monitor.download_diagnosis_report"}}</button>
<input name="seconds" size="3" maxlength="3" value="10"> {{ctx.Locale.Tr "tool.raw_seconds"}} <input name="seconds" size="3" maxlength="3" value="10"> {{ctx.Locale.Tr "tool.raw_seconds"}}

View file

@ -3,7 +3,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.users.user_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.users.user_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/users/new">{{ctx.Locale.Tr "admin.users.new_account"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/-/admin/users/new">{{ctx.Locale.Tr "admin.users.new_account"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">

View file

@ -6,7 +6,7 @@
{{if (or .ShowFooterVersion .PageIsAdmin)}} {{if (or .ShowFooterVersion .PageIsAdmin)}}
{{ctx.Locale.Tr "version"}}: {{ctx.Locale.Tr "version"}}:
{{if .IsAdmin}} {{if .IsAdmin}}
<a href="{{AppSubUrl}}/admin/config">{{AppVer}}</a> <a href="{{AppSubUrl}}/-/admin/config">{{AppVer}}</a>
{{else}} {{else}}
{{AppVer}} {{AppVer}}
{{end}} {{end}}

View file

@ -158,7 +158,7 @@
{{if .IsAdmin}} {{if .IsAdmin}}
<div class="divider"></div> <div class="divider"></div>
<a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/admin"> <a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/-/admin">
{{svg "octicon-server"}} {{svg "octicon-server"}}
{{ctx.Locale.Tr "admin_panel"}} {{ctx.Locale.Tr "admin_panel"}}
</a> </a>

View file

@ -29,15 +29,16 @@
<div class="default text">empty multiple dropdown</div> <div class="default text">empty multiple dropdown</div>
<div class="menu"> <div class="menu">
<div class="item">item</div> <div class="item">item</div>
</div> <div class="item">sm1</div>
</div> <div class="item">sm2</div>
<div class="ui multiple clearable search selection dropdown"> <div class="item">medium1</div>
<input type="hidden" value="1"> <div class="item">medium2</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}} <div class="item">large item1</div>
{{svg "octicon-x" 14 "remove icon"}} <div class="item">large item2</div>
<div class="default text">clearable search dropdown</div> <div class="item">large item3</div>
<div class="menu"> <div class="item">very large item test 1</div>
<div class="item" data-value="1">item</div> <div class="item">very large item test 2</div>
<div class="item">very large item test 3</div>
</div> </div>
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
@ -50,6 +51,27 @@
</div> </div>
</div> </div>
</div> </div>
<div>
<div class="ui multiple clearable search selection dropdown tw-max-w-[220px]">
<input type="hidden" value="1,2,3,4,5,10">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{svg "octicon-x" 14 "remove icon"}}
<div class="default text">clearable search dropdown</div>
<div class="menu">
<div class="item" data-value="1">item</div>
<div class="item" data-value="2">sm1</div>
<div class="item" data-value="3">sm2</div>
<div class="item" data-value="4">medium1</div>
<div class="item" data-value="5">medium2</div>
<div class="item" data-value="6">large item1</div>
<div class="item" data-value="7">large item2</div>
<div class="item" data-value="8">large item3</div>
<div class="item" data-value="9">very large item test 1</div>
<div class="item" data-value="10">very large item test 2</div>
<div class="item" data-value="11">very large item test 3</div>
</div>
</div>
</div>
<h2>Selection</h2> <h2>Selection</h2>
<div> <div>

View file

@ -24,7 +24,7 @@
</h4> </h4>
<div class="ui attached guide table segment empty-repo-guide"> <div class="ui attached guide table segment empty-repo-guide">
<div class="item"> <div class="item">
<h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository"}}</small></h3> <h3>{{ctx.Locale.Tr "repo.clone_this_repo"}} <small>{{ctx.Locale.Tr "repo.clone_helper" "http://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository"}}</small></h3>
<div class="repo-button-row"> <div class="repo-button-row">
{{if and .CanWriteCode (not .Repository.IsArchived)}} {{if and .CanWriteCode (not .Repository.IsArchived)}}

View file

@ -14,7 +14,7 @@
<div class="content tw-break-anywhere profile-avatar-name"> <div class="content tw-break-anywhere profile-avatar-name">
{{if .ContextUser.FullName}}<span class="header text center">{{.ContextUser.FullName}}</span>{{end}} {{if .ContextUser.FullName}}<span class="header text center">{{.ContextUser.FullName}}</span>{{end}}
<span class="username text center">{{.ContextUser.Name}} {{if .IsAdmin}} <span class="username text center">{{.ContextUser.Name}} {{if .IsAdmin}}
<a class="muted" href="{{AppSubUrl}}/admin/users/{{.ContextUser.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}"> <a class="muted" href="{{AppSubUrl}}/-/admin/users/{{.ContextUser.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">
{{svg "octicon-gear" 18}} {{svg "octicon-gear" 18}}
</a> </a>
{{end}}</span> {{end}}</span>

View file

@ -3444,107 +3444,125 @@
"operationId": "issueSearchIssues", "operationId": "issueSearchIssues",
"parameters": [ "parameters": [
{ {
"enum": [
"open",
"closed",
"all"
],
"type": "string", "type": "string",
"description": "whether issue is open or closed", "default": "open",
"description": "State of the issue",
"name": "state", "name": "state",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"description": "comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded", "description": "Comma-separated list of label names. Fetch only issues that have any of these labels. Non existent labels are discarded.",
"name": "labels", "name": "labels",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"description": "comma separated list of milestone names. Fetch only issues that have any of this milestones. Non existent are discarded", "description": "Comma-separated list of milestone names. Fetch only issues that have any of these milestones. Non existent milestones are discarded.",
"name": "milestones", "name": "milestones",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"description": "search string", "description": "Search string",
"name": "q", "name": "q",
"in": "query" "in": "query"
}, },
{ {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",
"description": "repository to prioritize in the results", "description": "Repository ID to prioritize in the results",
"name": "priority_repo_id", "name": "priority_repo_id",
"in": "query" "in": "query"
}, },
{ {
"enum": [
"issues",
"pulls"
],
"type": "string", "type": "string",
"description": "filter by type (issues / pulls) if set", "description": "Filter by issue type",
"name": "type", "name": "type",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format", "description": "Only show issues updated after the given time (RFC 3339 format)",
"name": "since", "name": "since",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format", "description": "Only show issues updated before the given time (RFC 3339 format)",
"name": "before", "name": "before",
"in": "query" "in": "query"
}, },
{ {
"type": "boolean", "type": "boolean",
"description": "filter (issues / pulls) assigned to you, default is false", "default": false,
"description": "Filter issues or pulls assigned to the authenticated user",
"name": "assigned", "name": "assigned",
"in": "query" "in": "query"
}, },
{ {
"type": "boolean", "type": "boolean",
"description": "filter (issues / pulls) created by you, default is false", "default": false,
"description": "Filter issues or pulls created by the authenticated user",
"name": "created", "name": "created",
"in": "query" "in": "query"
}, },
{ {
"type": "boolean", "type": "boolean",
"description": "filter (issues / pulls) mentioning you, default is false", "default": false,
"description": "Filter issues or pulls mentioning the authenticated user",
"name": "mentioned", "name": "mentioned",
"in": "query" "in": "query"
}, },
{ {
"type": "boolean", "type": "boolean",
"description": "filter pulls requesting your review, default is false", "default": false,
"description": "Filter pull requests where the authenticated user's review was requested",
"name": "review_requested", "name": "review_requested",
"in": "query" "in": "query"
}, },
{ {
"type": "boolean", "type": "boolean",
"description": "filter pulls reviewed by you, default is false", "default": false,
"description": "Filter pull requests reviewed by the authenticated user",
"name": "reviewed", "name": "reviewed",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"description": "filter by owner", "description": "Filter by repository owner",
"name": "owner", "name": "owner",
"in": "query" "in": "query"
}, },
{ {
"type": "string", "type": "string",
"description": "filter by team (requires organization owner parameter to be provided)", "description": "Filter by team (requires organization owner parameter)",
"name": "team", "name": "team",
"in": "query" "in": "query"
}, },
{ {
"minimum": 1,
"type": "integer", "type": "integer",
"description": "page number of results to return (1-based)", "default": 1,
"description": "Page number of results to return (1-based)",
"name": "page", "name": "page",
"in": "query" "in": "query"
}, },
{ {
"minimum": 0,
"type": "integer", "type": "integer",
"description": "page size of results", "description": "Number of items per page",
"name": "limit", "name": "limit",
"in": "query" "in": "query"
} }
@ -3552,6 +3570,12 @@
"responses": { "responses": {
"200": { "200": {
"$ref": "#/responses/IssueList" "$ref": "#/responses/IssueList"
},
"400": {
"$ref": "#/responses/error"
},
"422": {
"$ref": "#/responses/validationError"
} }
} }
} }

View file

@ -0,0 +1 @@
ref: refs/heads/master

View file

@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View file

@ -0,0 +1,8 @@
This repository will be used to test code search. The snippet below shows its directory structure
.
├── avocado.md
├── cucumber.md
├── ham.md
└── potato
└── ham.md

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
ORI_DIR=`pwd`
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
cd "$ORI_DIR"
for i in `ls "$SHELL_FOLDER/post-receive.d"`; do
sh "$SHELL_FOLDER/post-receive.d/$i"
done

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
ORI_DIR=`pwd`
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
cd "$ORI_DIR"
for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do
sh "$SHELL_FOLDER/pre-receive.d/$i"
done

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
ORI_DIR=`pwd`
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
cd "$ORI_DIR"
for i in `ls "$SHELL_FOLDER/proc-receive.d"`; do
sh "$SHELL_FOLDER/proc-receive.d/$i"
done

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" proc-receive

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