mirror of
https://github.com/go-gitea/gitea.git
synced 2024-11-04 08:17:24 -05:00
Merge branch 'main' into archived-lable-highglight-via-linear-gradient
This commit is contained in:
commit
eb86fcbdc5
13
cmd/hook.go
13
cmd/hook.go
@ -591,8 +591,9 @@ Gitea or set your environment appropriately.`, "")
|
||||
// S: ... ...
|
||||
// S: flush-pkt
|
||||
hookOptions := private.HookOptions{
|
||||
UserName: pusherName,
|
||||
UserID: pusherID,
|
||||
UserName: pusherName,
|
||||
UserID: pusherID,
|
||||
GitPushOptions: make(map[string]string),
|
||||
}
|
||||
hookOptions.OldCommitIDs = 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.GitPushOptions = make(map[string]string)
|
||||
|
||||
if hasPushOptions {
|
||||
for {
|
||||
rs, err = readPktLine(ctx, reader, pktLineTypeUnknow)
|
||||
@ -629,11 +628,7 @@ Gitea or set your environment appropriately.`, "")
|
||||
if rs.Type == pktLineTypeFlush {
|
||||
break
|
||||
}
|
||||
|
||||
kv := strings.SplitN(string(rs.Data), "=", 2)
|
||||
if len(kv) == 2 {
|
||||
hookOptions.GitPushOptions[kv[0]] = kv[1]
|
||||
}
|
||||
hookOptions.GitPushOptions.AddFromKeyValue(string(rs.Data))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,3 +712,24 @@
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
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
|
||||
|
@ -1768,3 +1768,34 @@
|
||||
size: 0
|
||||
is_fsck_enabled: true
|
||||
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
|
||||
|
@ -1517,3 +1517,40 @@
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
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
|
||||
|
@ -26,6 +26,7 @@ type PullRequestsOptions struct {
|
||||
SortType string
|
||||
Labels []int64
|
||||
MilestoneID int64
|
||||
PosterID int64
|
||||
}
|
||||
|
||||
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
||||
@ -46,6 +47,10 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
||||
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
||||
}
|
||||
|
||||
if opts.PosterID > 0 {
|
||||
sess.And("issue.poster_id=?", opts.PosterID)
|
||||
}
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
|
@ -138,12 +138,12 @@ func getTestCases() []struct {
|
||||
{
|
||||
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
|
||||
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",
|
||||
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",
|
||||
@ -158,7 +158,7 @@ func getTestCases() []struct {
|
||||
{
|
||||
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)},
|
||||
count: 33,
|
||||
count: 34,
|
||||
},
|
||||
{
|
||||
name: "AllTemplates",
|
||||
|
@ -408,6 +408,10 @@ func (u *User) IsIndividual() bool {
|
||||
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
|
||||
func (u *User) IsBot() bool {
|
||||
return u.Type == UserTypeBot
|
||||
@ -561,42 +565,43 @@ var (
|
||||
".",
|
||||
"..",
|
||||
".well-known",
|
||||
"admin",
|
||||
"api",
|
||||
"assets",
|
||||
"attachments",
|
||||
"avatar",
|
||||
"avatars",
|
||||
"captcha",
|
||||
"commits",
|
||||
"debug",
|
||||
"error",
|
||||
"explore",
|
||||
"favicon.ico",
|
||||
"ghost",
|
||||
"issues",
|
||||
"login",
|
||||
"manifest.json",
|
||||
"metrics",
|
||||
"milestones",
|
||||
"new",
|
||||
"notifications",
|
||||
"org",
|
||||
"pulls",
|
||||
"raw",
|
||||
"repo",
|
||||
|
||||
"api", // gitea api
|
||||
"metrics", // prometheus metrics api
|
||||
"v2", // container registry api
|
||||
|
||||
"assets", // static asset files
|
||||
"attachments", // issue attachments
|
||||
|
||||
"avatar", // avatar by email hash
|
||||
"avatars", // user avatars by file name
|
||||
"repo-avatars",
|
||||
"robots.txt",
|
||||
"search",
|
||||
"serviceworker.js",
|
||||
"ssh_info",
|
||||
|
||||
"captcha",
|
||||
"login", // oauth2 login
|
||||
"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",
|
||||
"user",
|
||||
"v2",
|
||||
"gitea-actions",
|
||||
|
||||
"ghost", // reserved name for deleted users (id: -1)
|
||||
"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"}
|
||||
)
|
||||
|
||||
|
@ -92,7 +92,10 @@ func TestSearchUsers(t *testing.T) {
|
||||
testOrgSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}},
|
||||
[]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{})
|
||||
|
||||
// test users
|
||||
|
@ -111,12 +111,12 @@ func SetExecutablePath(path string) error {
|
||||
|
||||
func ensureGitVersion() error {
|
||||
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||
moreHint := "get git: https://git-scm.com/download/"
|
||||
moreHint := "get git: https://git-scm.com/downloads"
|
||||
if runtime.GOOS == "linux" {
|
||||
// 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 {
|
||||
// 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)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"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"
|
||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
|
||||
@ -53,6 +54,7 @@ type RepoIndexerData struct {
|
||||
RepoID int64
|
||||
CommitID string
|
||||
Content string
|
||||
Filename string
|
||||
Language string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
@ -64,8 +66,10 @@ func (d *RepoIndexerData) Type() string {
|
||||
|
||||
const (
|
||||
repoIndexerAnalyzer = "repoIndexerAnalyzer"
|
||||
filenameIndexerAnalyzer = "filenameIndexerAnalyzer"
|
||||
filenameIndexerTokenizer = "filenameIndexerTokenizer"
|
||||
repoIndexerDocType = "repoIndexerDocType"
|
||||
repoIndexerLatestVersion = 6
|
||||
repoIndexerLatestVersion = 7
|
||||
)
|
||||
|
||||
// generateBleveIndexMapping generates a bleve index mapping for the repo indexer
|
||||
@ -79,6 +83,11 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
||||
textFieldMapping.IncludeInAll = false
|
||||
docMapping.AddFieldMappingsAt("Content", textFieldMapping)
|
||||
|
||||
fileNamedMapping := bleve.NewTextFieldMapping()
|
||||
fileNamedMapping.IncludeInAll = false
|
||||
fileNamedMapping.Analyzer = filenameIndexerAnalyzer
|
||||
docMapping.AddFieldMappingsAt("Filename", fileNamedMapping)
|
||||
|
||||
termFieldMapping := bleve.NewTextFieldMapping()
|
||||
termFieldMapping.IncludeInAll = false
|
||||
termFieldMapping.Analyzer = analyzer_keyword.Name
|
||||
@ -90,6 +99,7 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
||||
docMapping.AddFieldMappingsAt("UpdatedAt", timeFieldMapping)
|
||||
|
||||
mapping := bleve.NewIndexMapping()
|
||||
|
||||
if err := addUnicodeNormalizeTokenFilter(mapping); err != nil {
|
||||
return nil, err
|
||||
} else if err := mapping.AddCustomAnalyzer(repoIndexerAnalyzer, map[string]any{
|
||||
@ -100,6 +110,16 @@ func generateBleveIndexMapping() (mapping.IndexMapping, error) {
|
||||
}); err != nil {
|
||||
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.AddDocumentMapping(repoIndexerDocType, docMapping)
|
||||
mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping())
|
||||
@ -174,6 +194,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
|
||||
return batch.Index(id, &RepoIndexerData{
|
||||
RepoID: repo.ID,
|
||||
CommitID: commitSha,
|
||||
Filename: update.Filename,
|
||||
Content: string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
|
||||
Language: analyze.GetCodeLanguage(update.Filename, fileContents),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
@ -240,14 +261,19 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
keywordQuery query.Query
|
||||
)
|
||||
|
||||
phraseQuery := bleve.NewMatchPhraseQuery(opts.Keyword)
|
||||
phraseQuery.FieldVal = "Content"
|
||||
phraseQuery.Analyzer = repoIndexerAnalyzer
|
||||
keywordQuery = phraseQuery
|
||||
pathQuery := bleve.NewPrefixQuery(strings.ToLower(opts.Keyword))
|
||||
pathQuery.FieldVal = "Filename"
|
||||
pathQuery.SetBoost(10)
|
||||
|
||||
contentQuery := bleve.NewMatchQuery(opts.Keyword)
|
||||
contentQuery.FieldVal = "Content"
|
||||
|
||||
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 {
|
||||
repoQueries := make([]query.Query, 0, len(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()
|
||||
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
|
||||
|
||||
if len(opts.Language) == 0 {
|
||||
@ -307,6 +333,10 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
endIndex = locationEnd
|
||||
}
|
||||
}
|
||||
if len(hit.Locations["Filename"]) > 0 {
|
||||
startIndex, endIndex = internal.FilenameMatchIndexPos(hit.Fields["Content"].(string))
|
||||
}
|
||||
|
||||
language := hit.Fields["Language"].(string)
|
||||
var updatedUnix timeutil.TimeStamp
|
||||
if t, err := time.Parse(time.RFC3339, hit.Fields["UpdatedAt"].(string)); err == nil {
|
||||
|
101
modules/indexer/code/bleve/token/path/path.go
Normal file
101
modules/indexer/code/bleve/token/path/path.go
Normal 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)
|
||||
}
|
76
modules/indexer/code/bleve/token/path/path_test.go
Normal file
76
modules/indexer/code/bleve/token/path/path_test.go
Normal 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))
|
||||
}
|
@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
esRepoIndexerLatestVersion = 1
|
||||
esRepoIndexerLatestVersion = 2
|
||||
// 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
|
||||
esMultiMatchTypeBestFields = "best_fields"
|
||||
@ -57,12 +57,50 @@ func NewIndexer(url, indexerName string) *Indexer {
|
||||
|
||||
const (
|
||||
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": {
|
||||
"properties": {
|
||||
"repo_id": {
|
||||
"type": "long",
|
||||
"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": {
|
||||
"type": "text",
|
||||
"term_vector": "with_positions_offsets",
|
||||
@ -136,6 +174,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro
|
||||
Id(id).
|
||||
Doc(map[string]any{
|
||||
"repo_id": repo.ID,
|
||||
"filename": update.Filename,
|
||||
"content": string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})),
|
||||
"commit_id": sha,
|
||||
"language": analyze.GetCodeLanguage(update.Filename, fileContents),
|
||||
@ -231,11 +270,11 @@ func (b *Indexer) doDelete(ctx context.Context, repoID int64) error {
|
||||
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
|
||||
// first end following the start string.
|
||||
// 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)
|
||||
if startIdx < 0 {
|
||||
return -1, -1
|
||||
@ -244,22 +283,29 @@ func indexPos(content, start, end string) (int, int) {
|
||||
if endIdx < 0 {
|
||||
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) {
|
||||
hits := make([]*internal.SearchResult, 0, pageSize)
|
||||
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.
|
||||
// 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
|
||||
var startIndex, endIndex int
|
||||
c, ok := hit.Highlight["content"]
|
||||
if ok && len(c) > 0 {
|
||||
if c, ok := hit.Highlight["filename"]; 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,
|
||||
// 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?
|
||||
startIndex, endIndex = indexPos(c[0], "<em>", "</em>")
|
||||
startIndex, endIndex = contentMatchIndexPos(c[0], "<em>", "</em>")
|
||||
if startIndex == -1 {
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)),
|
||||
Language: language,
|
||||
StartIndex: startIndex,
|
||||
EndIndex: endIndex - 9, // remove the length <em></em> since we give Content the original data
|
||||
EndIndex: endIndex,
|
||||
Color: enry.GetColor(language),
|
||||
})
|
||||
}
|
||||
@ -315,7 +355,10 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
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 = query.Must(kwQuery)
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
@ -341,6 +384,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
Highlight(
|
||||
elastic.NewHighlight().
|
||||
Field("content").
|
||||
Field("filename").
|
||||
NumOfFragments(0). // return all highting content on fragments
|
||||
HighlighterType("fvh"),
|
||||
).
|
||||
@ -373,6 +417,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
||||
Highlight(
|
||||
elastic.NewHighlight().
|
||||
Field("content").
|
||||
Field("filename").
|
||||
NumOfFragments(0). // return all highting content on fragments
|
||||
HighlighterType("fvh"),
|
||||
).
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
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, 24, endIdx)
|
||||
assert.EqualValues(t, 15, endIdx)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package code
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
@ -20,53 +21,166 @@ import (
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type codeSearchResult struct {
|
||||
Filename string
|
||||
Content string
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
||||
|
||||
func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var repoID int64 = 1
|
||||
err := index(git.DefaultContext, indexer, repoID)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, setupRepositoryIndexes(git.DefaultContext, indexer))
|
||||
|
||||
keywords := []struct {
|
||||
RepoIDs []int64
|
||||
Keyword string
|
||||
IDs []int64
|
||||
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,
|
||||
Keyword: "Description",
|
||||
IDs: []int64{repoID},
|
||||
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},
|
||||
Keyword: "Description",
|
||||
IDs: []int64{},
|
||||
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,
|
||||
Keyword: "repo1",
|
||||
IDs: []int64{repoID},
|
||||
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},
|
||||
Keyword: "repo1",
|
||||
IDs: []int64{},
|
||||
Langs: 0,
|
||||
},
|
||||
// Search for a non-existing term.
|
||||
// This scenario yields no results
|
||||
{
|
||||
RepoIDs: nil,
|
||||
Keyword: "non-exist",
|
||||
IDs: []int64{},
|
||||
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 {
|
||||
@ -81,19 +195,37 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
||||
IsKeywordFuzzy: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, kw.IDs, int(total))
|
||||
assert.Len(t, langs, kw.Langs)
|
||||
|
||||
ids := make([]int64, 0, len(res))
|
||||
for _, hit := range res {
|
||||
ids = append(ids, hit.RepoID)
|
||||
assert.EqualValues(t, "# repo1\n\nDescription for repo1", hit.Content)
|
||||
hits := make([]codeSearchResult, 0, len(res))
|
||||
|
||||
if total > 0 {
|
||||
assert.NotEmpty(t, kw.Results, "The given scenario does not provide any expected results")
|
||||
}
|
||||
|
||||
for _, hit := range res {
|
||||
hits = append(hits, codeSearchResult{
|
||||
Filename: hit.Filename,
|
||||
Content: hit.Content,
|
||||
})
|
||||
}
|
||||
|
||||
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.EqualValues(t, kw.IDs, ids)
|
||||
})
|
||||
}
|
||||
|
||||
assert.NoError(t, indexer.Delete(context.Background(), repoID))
|
||||
assert.NoError(t, tearDownRepositoryIndexes(indexer))
|
||||
})
|
||||
}
|
||||
|
||||
@ -136,3 +268,25 @@ func TestESIndexAndSearch(t *testing.T) {
|
||||
|
||||
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}
|
||||
}
|
||||
|
@ -10,6 +10,10 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
const (
|
||||
filenameMatchNumberOfLines = 7 // Copied from github search
|
||||
)
|
||||
|
||||
func FilenameIndexerID(repoID int64, filename string) string {
|
||||
return internal.Base36(repoID) + "_" + filename
|
||||
}
|
||||
@ -30,3 +34,17 @@ func FilenameOfIndexerID(indexerID string) string {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -11,10 +11,15 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/v2/index/upsidedown"
|
||||
"github.com/ethantkoenig/rupture"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFuzziness = 2
|
||||
)
|
||||
|
||||
// openIndexer open the index at the specified path, checking for metadata
|
||||
// updates and bleve version updates. If index needs to be created (or
|
||||
// re-created), returns (nil, nil)
|
||||
@ -48,7 +53,27 @@ func openIndexer(path string, latestVersion int) (bleve.Index, int, error) {
|
||||
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 {
|
||||
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
|
||||
// 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.
|
||||
@ -57,5 +82,5 @@ func GuessFuzzinessByKeyword(s string) int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return min(2, len(s)/4)
|
||||
return min(maxFuzziness, len(s)/4)
|
||||
}
|
||||
|
45
modules/indexer/internal/bleve/util_test.go
Normal file
45
modules/indexer/internal/bleve/util_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ type PullRequest struct {
|
||||
ForeignIndex int64
|
||||
Context DownloaderContext `yaml:"-"`
|
||||
EnsuredSafe bool `yaml:"ensured_safe"`
|
||||
IsDraft bool `yaml:"is_draft"`
|
||||
}
|
||||
|
||||
func (p *PullRequest) GetLocalIndex() int64 { return p.Number }
|
||||
|
@ -7,11 +7,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@ -24,25 +22,6 @@ const (
|
||||
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
|
||||
type HookOptions struct {
|
||||
OldCommitIDs []string
|
||||
|
45
modules/private/pushoptions.go
Normal file
45
modules/private/pushoptions.go
Normal 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]] = ""
|
||||
}
|
||||
}
|
30
modules/private/pushoptions_test.go
Normal file
30
modules/private/pushoptions_test.go
Normal 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())
|
||||
}
|
@ -217,13 +217,14 @@ const (
|
||||
|
||||
// IssueCommentPayload represents a payload information of issue comment event.
|
||||
type IssueCommentPayload struct {
|
||||
Action HookIssueCommentAction `json:"action"`
|
||||
Issue *Issue `json:"issue"`
|
||||
Comment *Comment `json:"comment"`
|
||||
Changes *ChangesPayload `json:"changes,omitempty"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Sender *User `json:"sender"`
|
||||
IsPull bool `json:"is_pull"`
|
||||
Action HookIssueCommentAction `json:"action"`
|
||||
Issue *Issue `json:"issue"`
|
||||
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||
Comment *Comment `json:"comment"`
|
||||
Changes *ChangesPayload `json:"changes,omitempty"`
|
||||
Repository *Repository `json:"repository"`
|
||||
Sender *User `json:"sender"`
|
||||
IsPull bool `json:"is_pull"`
|
||||
}
|
||||
|
||||
// JSONPayload implements Payload
|
||||
|
@ -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_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 d’utilisateur 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é
|
||||
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.
|
||||
@ -1039,6 +1041,7 @@ issue_labels_helper=Sélectionner un jeu de label.
|
||||
license=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>.
|
||||
multiple_licenses=Licences multiples
|
||||
object_format=Format d'objet
|
||||
object_format_helper=Format d’objet pour ce dépôt. Ne peut être modifié plus tard. SHA1 est le plus compatible.
|
||||
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_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.blocked_by_approvals=Cette demande d'ajout n’est pas suffisamment approuvée. %d approbations obtenues sur %d.
|
||||
pulls.blocked_by_approvals=Cette demande d’ajout n’est pas suffisamment approuvée. %d approbations obtenues sur %d.
|
||||
pulls.blocked_by_approvals_whitelisted=Cette demande d’ajout n’a pas encore assez d’approbations. %d sur %d approbations de la part des utilisateurs ou équipes sur la liste autorisée.
|
||||
pulls.blocked_by_rejection=Cette demande d’ajout nécessite des corrections sollicitées par un évaluateur officiel.
|
||||
pulls.blocked_by_official_review_requests=Cette demande d’ajout 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_tag.started=Synchronisation des étiquettes
|
||||
dashboard.rebuild_issue_indexer=Reconstruire l’indexeur des tickets
|
||||
dashboard.sync_repo_licenses=Synchroniser les licences du dépôt
|
||||
|
||||
users.user_manage_panel=Gestion du compte utilisateur
|
||||
users.new_account=Créer un compte
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -580,6 +580,8 @@ lang_select_error=Escolha um idioma da lista.
|
||||
|
||||
username_been_taken=O nome de utilizador já foi tomado.
|
||||
username_change_not_local_user=Utilizadores que não são locais não têm permissão para mudar o nome de utilizador.
|
||||
change_username_disabled=Alterar o nome de utilizador está desabilitado.
|
||||
change_full_name_disabled=Alterar o nome completo está desabilitado.
|
||||
username_has_not_been_changed=O nome de utilizador não foi modificado
|
||||
repo_name_been_taken=O nome do repositório já foi usado.
|
||||
repository_force_private=Forçar Privado está habilitado: repositórios privados não podem ser tornados públicos.
|
||||
@ -705,6 +707,8 @@ public_profile=Perfil público
|
||||
biography_placeholder=Conte-nos um pouco sobre si! (Pode usar Markdown)
|
||||
location_placeholder=Partilhe a sua localização aproximada com outros
|
||||
profile_desc=Controle como o seu perfil é apresentado aos outros utilizadores. O seu endereço de email principal será usado para notificações, recuperação de senha e operações Git baseadas na web.
|
||||
password_username_disabled=Não tem permissão para alterar os nomes de utilizador deles/delas. Entre em contacto com o administrador para saber mais detalhes.
|
||||
password_full_name_disabled=Não tem permissão para alterar o nome completo deles/delas. Entre em contacto com o administrador para saber mais detalhes.
|
||||
full_name=Nome completo
|
||||
website=Sítio web
|
||||
location=Localização
|
||||
|
@ -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")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
container_model "code.gitea.io/gitea/models/packages/container"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
@ -22,8 +22,6 @@ import (
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
)
|
||||
|
||||
var uploadVersionMutex sync.Mutex
|
||||
|
||||
// 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
|
||||
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) {
|
||||
var uploadVersion *packages_model.PackageVersion
|
||||
|
||||
// FIXME: Replace usage of mutex with database transaction
|
||||
// https://github.com/go-gitea/gitea/pull/21862
|
||||
uploadVersionMutex.Lock()
|
||||
err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
releaser, err := globallock.Lock(ctx, containerPkgName(pi.Owner.ID, pi.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer releaser()
|
||||
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
created := true
|
||||
p := &packages_model.Package{
|
||||
OwnerID: pi.Owner.ID,
|
||||
@ -140,7 +145,6 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
|
||||
|
||||
return nil
|
||||
})
|
||||
uploadVersionMutex.Unlock()
|
||||
|
||||
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 {
|
||||
releaser, err := globallock.Lock(ctx, containerPkgName(ownerID, image))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer releaser()
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
pfds, err := container_model.GetContainerBlobs(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
|
@ -45,7 +45,7 @@ func ListHooks(ctx *context.APIContext) {
|
||||
}
|
||||
hooks := make([]*api.Hook, len(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 {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
|
||||
return
|
||||
@ -83,7 +83,7 @@ func GetHook(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
h, err := webhook_service.ToHook("/admin/", hook)
|
||||
h, err := webhook_service.ToHook("/-/admin/", hook)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
|
||||
return
|
||||
|
@ -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 not being used, reqToken will enforce other sign in methods
|
||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
||||
@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
|
||||
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
|
||||
|
||||
// use the http method to determine the access level
|
||||
requiredScopeLevel := auth_model.Read
|
||||
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
|
||||
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
|
||||
publicOnly, err := scope.PublicOnly()
|
||||
@ -269,21 +334,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
return
|
||||
}
|
||||
|
||||
// this context is used by the middleware in the specific route
|
||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
|
||||
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))
|
||||
// assign to true so that those searching should only filter public repositories/users/organizations
|
||||
ctx.PublicOnly = publicOnly
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -879,11 +912,11 @@ func Routes() *web.Router {
|
||||
m.Group("/user/{username}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
m.Group("/user-id/{user-id}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserIDAssignmentAPI())
|
||||
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
|
||||
}
|
||||
|
||||
@ -939,7 +972,7 @@ func Routes() *web.Router {
|
||||
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
||||
|
||||
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
||||
}, context.UserAssignmentAPI(), individualPermsChecker)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
||||
|
||||
// Users (requires user scope)
|
||||
@ -957,7 +990,7 @@ func Routes() *web.Router {
|
||||
m.Get("/starred", user.GetStarredRepos)
|
||||
|
||||
m.Get("/subscriptions", user.GetWatchedRepos)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
|
||||
// Users (requires user scope)
|
||||
@ -1044,7 +1077,7 @@ func Routes() *web.Router {
|
||||
m.Get("", user.IsStarring)
|
||||
m.Put("", user.Star)
|
||||
m.Delete("", user.Unstar)
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
m.Get("/times", repo.ListMyTrackedTimes)
|
||||
m.Get("/stopwatches", repo.GetStopwatches)
|
||||
@ -1069,18 +1102,20 @@ func Routes() *web.Router {
|
||||
m.Get("", user.CheckUserBlock)
|
||||
m.Put("", user.BlockUser)
|
||||
m.Delete("", user.UnblockUser)
|
||||
}, context.UserAssignmentAPI())
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
})
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
|
||||
// Repositories (requires repo scope, org scope)
|
||||
m.Post("/org/{org}/repos",
|
||||
// FIXME: we need org in context
|
||||
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
||||
reqToken(),
|
||||
bind(api.CreateRepoOption{}),
|
||||
repo.CreateOrgRepoDeprecated)
|
||||
|
||||
// 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)
|
||||
|
||||
// Repos (requires repo scope)
|
||||
@ -1334,7 +1369,7 @@ func Routes() *web.Router {
|
||||
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
||||
m.Delete("", repo.DeleteAvatar)
|
||||
}, reqAdmin(), reqToken())
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
|
||||
// Notifications (requires notifications scope)
|
||||
@ -1343,7 +1378,7 @@ func Routes() *web.Router {
|
||||
m.Combo("/notifications", reqToken()).
|
||||
Get(notify.ListRepoNotifications).
|
||||
Put(notify.ReadRepoNotifications)
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
||||
|
||||
// Issue (requires issue scope)
|
||||
@ -1457,7 +1492,7 @@ func Routes() *web.Router {
|
||||
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
||||
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
||||
})
|
||||
}, repoAssignment())
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
||||
|
||||
// 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("/", 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
|
||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
||||
m.Group("/users/{username}/orgs", func() {
|
||||
m.Get("", reqToken(), org.ListUserOrgs)
|
||||
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.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
||||
m.Group("/orgs/{org}", func() {
|
||||
@ -1533,7 +1568,7 @@ func Routes() *web.Router {
|
||||
m.Delete("", org.UnblockUser)
|
||||
})
|
||||
}, reqToken(), reqOrgOwnership())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||
m.Group("/teams/{teamid}", func() {
|
||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||
@ -1553,7 +1588,7 @@ func Routes() *web.Router {
|
||||
Get(reqToken(), org.GetTeamRepo)
|
||||
})
|
||||
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("/cron", func() {
|
||||
|
@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/OrganizationList"
|
||||
|
||||
vMode := []api.VisibleType{api.VisibleTypePublic}
|
||||
if ctx.IsSigned {
|
||||
if ctx.IsSigned && !ctx.PublicOnly {
|
||||
vMode = append(vMode, api.VisibleTypeLimited)
|
||||
if ctx.Doer.IsAdmin {
|
||||
vMode = append(vMode, api.VisibleTypePrivate)
|
||||
|
@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
Actor: ctx.Doer,
|
||||
}
|
||||
if ctx.IsSigned {
|
||||
opts.Private = true
|
||||
opts.Private = !ctx.PublicOnly
|
||||
opts.AllLimited = true
|
||||
}
|
||||
if ctx.FormString("owner") != "" {
|
||||
|
@ -52,56 +52,79 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// description: Owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// description: Name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: state
|
||||
// in: query
|
||||
// description: "State of pull request: open or closed (optional)"
|
||||
// description: State of pull request
|
||||
// type: string
|
||||
// enum: [closed, open, all]
|
||||
// enum: [open, closed, all]
|
||||
// default: open
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: "Type of sort"
|
||||
// description: Type of sort
|
||||
// type: string
|
||||
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
|
||||
// - name: milestone
|
||||
// in: query
|
||||
// description: "ID of the milestone"
|
||||
// description: ID of the milestone
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: labels
|
||||
// in: query
|
||||
// description: "Label IDs"
|
||||
// description: Label IDs
|
||||
// type: array
|
||||
// collectionFormat: multi
|
||||
// items:
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: poster
|
||||
// in: query
|
||||
// description: Filter by pull request author
|
||||
// type: string
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// description: Page number of results to return (1-based)
|
||||
// type: integer
|
||||
// minimum: 1
|
||||
// default: 1
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// description: Page size of results
|
||||
// type: integer
|
||||
// minimum: 0
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PullRequestList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "500":
|
||||
// "$ref": "#/responses/error"
|
||||
|
||||
labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels"))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
||||
return
|
||||
}
|
||||
var posterID int64
|
||||
if posterStr := ctx.FormString("poster"); posterStr != "" {
|
||||
poster, err := user_model.GetUserByName(ctx, posterStr)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Error(http.StatusBadRequest, "Poster not found", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
posterID = poster.ID
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
|
||||
ListOptions: listOptions,
|
||||
@ -109,6 +132,7 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||
SortType: ctx.FormTrim("sort"),
|
||||
Labels: labelIDs,
|
||||
MilestoneID: ctx.FormInt64("milestone"),
|
||||
PosterID: posterID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
||||
|
@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) {
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
||||
if ctx.PublicOnly {
|
||||
private = false
|
||||
}
|
||||
|
||||
opts := &repo_model.SearchRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Actor: ctx.Doer,
|
||||
@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) {
|
||||
TeamID: ctx.FormInt64("team_id"),
|
||||
TopicOnly: ctx.FormBool("topic"),
|
||||
Collaborate: optional.None[bool](),
|
||||
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
||||
Private: private,
|
||||
Template: optional.None[bool](),
|
||||
StarredByID: ctx.FormInt64("starredBy"),
|
||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
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/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@ -67,12 +68,17 @@ func Search(ctx *context.APIContext) {
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewActionsUser()}
|
||||
default:
|
||||
var visible []structs.VisibleType
|
||||
if ctx.PublicOnly {
|
||||
visible = []structs.VisibleType{structs.VisibleTypePublic}
|
||||
}
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: uid,
|
||||
Type: user_model.UserTypeIndividual,
|
||||
SearchByEmail: true,
|
||||
Visible: visible,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -100,7 +100,7 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
|
||||
func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) {
|
||||
hook, ok := addHook(ctx, form, 0, 0)
|
||||
if ok {
|
||||
h, err := webhook_service.ToHook(setting.AppSubURL+"/admin", hook)
|
||||
h, err := webhook_service.ToHook(setting.AppSubURL+"/-/admin", hook)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
|
||||
return
|
||||
@ -268,7 +268,7 @@ func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID in
|
||||
ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err)
|
||||
return
|
||||
}
|
||||
h, err := webhook_service.ToHook(setting.AppURL+"/admin", updated)
|
||||
h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", updated)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "convert.ToHook", err)
|
||||
return
|
||||
|
@ -208,7 +208,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
||||
return
|
||||
}
|
||||
|
||||
cols := make([]string, 0, len(opts.GitPushOptions))
|
||||
cols := make([]string, 0, 2)
|
||||
|
||||
if isPrivate.Has() {
|
||||
repo.IsPrivate = isPrivate.Value()
|
||||
|
@ -185,9 +185,9 @@ func DashboardPost(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
if form.From == "monitor" {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/monitor/cron")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/monitor/cron")
|
||||
} else {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,8 @@ var (
|
||||
func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers {
|
||||
return &user_setting.OAuth2CommonHandlers{
|
||||
OwnerID: 0,
|
||||
BasePathList: fmt.Sprintf("%s/admin/applications", setting.AppSubURL),
|
||||
BasePathEditPrefix: fmt.Sprintf("%s/admin/applications/oauth2", setting.AppSubURL),
|
||||
BasePathList: fmt.Sprintf("%s/-/admin/applications", setting.AppSubURL),
|
||||
BasePathEditPrefix: fmt.Sprintf("%s/-/admin/applications/oauth2", setting.AppSubURL),
|
||||
TplAppEdit: tplSettingsOauth2ApplicationEdit,
|
||||
}
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ func NewAuthSourcePost(ctx *context.Context) {
|
||||
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.Redirect(setting.AppSubURL + "/admin/auths")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/auths")
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
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
|
||||
@ -454,11 +454,11 @@ func DeleteAuthSource(ctx *context.Context) {
|
||||
} else {
|
||||
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
|
||||
}
|
||||
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success"))
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths")
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/auths")
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func SendTestMail(ctx *context.Context) {
|
||||
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
|
||||
@ -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 {
|
||||
|
@ -134,7 +134,7 @@ func ActivateEmail(ctx *context.Context) {
|
||||
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{}
|
||||
if val := ctx.FormTrim("q"); len(val) > 0 {
|
||||
q.Set("q", val)
|
||||
|
@ -36,8 +36,8 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
|
||||
sys["Title"] = ctx.Tr("admin.systemhooks")
|
||||
sys["Description"] = ctx.Tr("admin.systemhooks.desc", "https://docs.gitea.com/usage/webhooks")
|
||||
sys["Webhooks"], err = webhook.GetSystemWebhooks(ctx, optional.None[bool]())
|
||||
sys["BaseLink"] = setting.AppSubURL + "/admin/hooks"
|
||||
sys["BaseLinkNew"] = setting.AppSubURL + "/admin/system-hooks"
|
||||
sys["BaseLink"] = setting.AppSubURL + "/-/admin/hooks"
|
||||
sys["BaseLinkNew"] = setting.AppSubURL + "/-/admin/system-hooks"
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebhooksAdmin", err)
|
||||
return
|
||||
@ -46,8 +46,8 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
|
||||
def["Title"] = ctx.Tr("admin.defaulthooks")
|
||||
def["Description"] = ctx.Tr("admin.defaulthooks.desc", "https://docs.gitea.com/usage/webhooks")
|
||||
def["Webhooks"], err = webhook.GetDefaultWebhooks(ctx)
|
||||
def["BaseLink"] = setting.AppSubURL + "/admin/hooks"
|
||||
def["BaseLinkNew"] = setting.AppSubURL + "/admin/default-hooks"
|
||||
def["BaseLink"] = setting.AppSubURL + "/-/admin/hooks"
|
||||
def["BaseLinkNew"] = setting.AppSubURL + "/-/admin/default-hooks"
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebhooksAdmin", err)
|
||||
return
|
||||
@ -67,5 +67,5 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/admin/hooks")
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/hooks")
|
||||
}
|
||||
|
@ -74,5 +74,5 @@ func EmptyNotices(ctx *context.Context) {
|
||||
|
||||
log.Trace("System notices deleted by admin (%s): [start: %d]", ctx.Doer.Name, 0)
|
||||
ctx.Flash.Success(ctx.Tr("admin.notices.delete_success"))
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/notices")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/notices")
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func DeletePackageVersion(ctx *context.Context) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -109,5 +109,5 @@ func CleanupExpiredData(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.packages.cleanup.success"))
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/packages")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/packages")
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func QueueSet(ctx *context.Context) {
|
||||
maxNumber, err = strconv.Atoi(maxNumberStr)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
if maxNumber < -1 {
|
||||
@ -65,7 +65,7 @@ func QueueSet(ctx *context.Context) {
|
||||
|
||||
mq.SetWorkerMaxNumber(maxNumber)
|
||||
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) {
|
||||
@ -85,5 +85,5 @@ func QueueRemoveAllItems(ctx *context.Context) {
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func DeleteRepo(ctx *context.Context) {
|
||||
log.Trace("Repository deleted: %s", repo.FullName())
|
||||
|
||||
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
|
||||
@ -114,7 +114,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
|
||||
|
||||
dirSplit := strings.SplitN(dir, "/", 2)
|
||||
if len(dirSplit) != 2 {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/repos")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/repos")
|
||||
return
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
log.Debug("User does not exist: %s", dirSplit[0])
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/repos")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/repos")
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
@ -160,5 +160,5 @@ func AdoptOrDeleteRepository(ctx *context.Context) {
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
@ -9,5 +9,5 @@ import (
|
||||
)
|
||||
|
||||
func RedirectToDefaultSetting(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/actions/runners")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/actions/runners")
|
||||
}
|
||||
|
@ -42,5 +42,5 @@ func Stacktrace(ctx *context.Context) {
|
||||
func StacktraceCancel(ctx *context.Context) {
|
||||
pid := ctx.PathParam("pid")
|
||||
process.GetManager().Cancel(process.IDType(pid))
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/admin/monitor/stacktrace")
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/-/admin/monitor/stacktrace")
|
||||
}
|
||||
|
@ -215,14 +215,14 @@ func NewUserPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
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 {
|
||||
u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64(":userid"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/users")
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin/users")
|
||||
} else {
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
}
|
||||
@ -481,7 +481,7 @@ func EditUserPost(ctx *context.Context) {
|
||||
}
|
||||
|
||||
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
|
||||
@ -495,7 +495,7 @@ func DeleteUser(ctx *context.Context) {
|
||||
// admin should not delete themself
|
||||
if u.ID == ctx.Doer.ID {
|
||||
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
|
||||
}
|
||||
|
||||
@ -503,16 +503,16 @@ func DeleteUser(ctx *context.Context) {
|
||||
switch {
|
||||
case models.IsErrUserOwnRepos(err):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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:
|
||||
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)
|
||||
|
||||
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
|
||||
@ -538,7 +538,7 @@ func AvatarPost(ctx *context.Context) {
|
||||
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
|
||||
@ -552,5 +552,5 @@ func DeleteAvatar(ctx *context.Context) {
|
||||
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))
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
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)
|
||||
}
|
||||
|
||||
// Clear whatever CSRF cookie has right now, force to generate a new one
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
// force to generate a new CSRF token
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
|
||||
// Register last login
|
||||
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
|
||||
}
|
||||
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
|
||||
if err := resetLocale(ctx, user); err != nil {
|
||||
ctx.ServerError("resetLocale", err)
|
||||
return
|
||||
|
@ -358,8 +358,8 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
||||
return
|
||||
}
|
||||
|
||||
// Clear whatever CSRF cookie has right now, force to generate a new one
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
// force to generate a new CSRF token
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
|
||||
if err := resetLocale(ctx, u); err != nil {
|
||||
ctx.ServerError("resetLocale", err)
|
||||
|
@ -166,7 +166,7 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
|
||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||
headBranchLink := ""
|
||||
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 {
|
||||
case err == nil:
|
||||
if !b.IsDeleted {
|
||||
@ -887,8 +887,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
|
||||
}
|
||||
|
||||
if pull.HeadRepo != nil {
|
||||
ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/commit/" + endCommitID
|
||||
|
||||
if !pull.HasMerged && ctx.Doer != nil {
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
|
@ -76,7 +76,7 @@ func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) {
|
||||
IsAdmin: true,
|
||||
RunnersTemplate: tplAdminRunners,
|
||||
RunnerEditTemplate: tplAdminRunnerEdit,
|
||||
RedirectLink: setting.AppSubURL + "/admin/actions/runners/",
|
||||
RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) {
|
||||
RepoID: 0,
|
||||
IsGlobal: true,
|
||||
VariablesTemplate: tplAdminVariables,
|
||||
RedirectLink: setting.AppSubURL + "/admin/actions/variables",
|
||||
RedirectLink: setting.AppSubURL + "/-/admin/actions/variables",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -100,8 +100,8 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
|
||||
return &ownerRepoCtx{
|
||||
IsAdmin: true,
|
||||
IsSystemWebhook: ctx.PathParam(":configType") == "system-hooks",
|
||||
Link: path.Join(setting.AppSubURL, "/admin/hooks"),
|
||||
LinkNew: path.Join(setting.AppSubURL, "/admin/", ctx.PathParam(":configType")),
|
||||
Link: path.Join(setting.AppSubURL, "/-/admin/hooks"),
|
||||
LinkNew: path.Join(setting.AppSubURL, "/-/admin/", ctx.PathParam(":configType")),
|
||||
NewTemplate: tplAdminHookNew,
|
||||
}, nil
|
||||
}
|
||||
|
@ -683,7 +683,7 @@ func registerRoutes(m *web.Router) {
|
||||
adminReq := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true, AdminRequired: true})
|
||||
|
||||
// ***** START: Admin *****
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/-/admin", func() {
|
||||
m.Get("", admin.Dashboard)
|
||||
m.Get("/system_status", admin.SystemStatus)
|
||||
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
|
||||
@ -1461,6 +1461,35 @@ func registerRoutes(m *web.Router) {
|
||||
)
|
||||
// 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("/activity_author_data", func() {
|
||||
m.Get("", repo.ActivityAuthors)
|
||||
@ -1499,32 +1528,6 @@ func registerRoutes(m *web.Router) {
|
||||
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.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
|
||||
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
|
||||
|
@ -116,11 +116,20 @@ func (input *notifyInput) Notify(ctx context.Context) {
|
||||
}
|
||||
|
||||
func notify(ctx context.Context, input *notifyInput) error {
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
if input.Doer.IsActions() {
|
||||
// avoiding triggering cyclically, for example:
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
if input.Repo.IsEmpty || input.Repo.IsArchived {
|
||||
@ -174,7 +183,6 @@ func notify(ctx context.Context, input *notifyInput) error {
|
||||
|
||||
var detectedWorkflows []*actions_module.DetectedWorkflow
|
||||
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,
|
||||
input.Event,
|
||||
input.Payload,
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
@ -24,10 +23,10 @@ import (
|
||||
// ProcReceive handle proc receive work
|
||||
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))
|
||||
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
|
||||
topicBranch := opts.GitPushOptions["topic"]
|
||||
forcePush, _ := strconv.ParseBool(opts.GitPushOptions["force-push"])
|
||||
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)
|
||||
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()
|
||||
curentTopicBranch := ""
|
||||
currentTopicBranch := ""
|
||||
if !gitRepo.IsBranchExist(baseBranchName) {
|
||||
// try match refs/for/<target-branch>/<topic-branch>
|
||||
for p, v := range baseBranchName {
|
||||
if v == '/' && gitRepo.IsBranchExist(baseBranchName[:p]) && p != len(baseBranchName)-1 {
|
||||
curentTopicBranch = baseBranchName[p+1:]
|
||||
currentTopicBranch = baseBranchName[p+1:]
|
||||
baseBranchName = baseBranchName[:p]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(topicBranch) == 0 && len(curentTopicBranch) == 0 {
|
||||
if len(topicBranch) == 0 && len(currentTopicBranch) == 0 {
|
||||
results = append(results, private.HookProcReceiveRefResult{
|
||||
OriginalRef: opts.RefFullNames[i],
|
||||
OldOID: opts.OldCommitIDs[i],
|
||||
@ -78,18 +77,18 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
continue
|
||||
}
|
||||
|
||||
if len(curentTopicBranch) == 0 {
|
||||
curentTopicBranch = topicBranch
|
||||
if len(currentTopicBranch) == 0 {
|
||||
currentTopicBranch = topicBranch
|
||||
}
|
||||
|
||||
// because different user maybe want to use same topic,
|
||||
// So it's better to make sure the topic branch name
|
||||
// has user name prefix
|
||||
// has username prefix
|
||||
var headBranch string
|
||||
if !strings.HasPrefix(curentTopicBranch, userName+"/") {
|
||||
headBranch = userName + "/" + curentTopicBranch
|
||||
if !strings.HasPrefix(currentTopicBranch, userName+"/") {
|
||||
headBranch = userName + "/" + currentTopicBranch
|
||||
} else {
|
||||
headBranch = curentTopicBranch
|
||||
headBranch = currentTopicBranch
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !forcePush {
|
||||
if !forcePush.Value() {
|
||||
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").
|
||||
AddDynamicArguments(oldCommitID, "^"+opts.NewCommitIDs[i]).
|
||||
RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: os.Environ()})
|
||||
|
@ -103,8 +103,8 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
|
||||
|
||||
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 {
|
||||
ctx.Csrf.DeleteCookie(ctx)
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,10 @@ type APIContext struct {
|
||||
|
||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||
|
||||
Repo *Repository
|
||||
Org *APIOrganization
|
||||
Package *Package
|
||||
Repo *Repository
|
||||
Org *APIOrganization
|
||||
Package *Package
|
||||
PublicOnly bool // Whether the request is for a public endpoint
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -129,10 +129,8 @@ func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
||||
}
|
||||
|
||||
if needsNew {
|
||||
// FIXME: actionId.
|
||||
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
|
||||
cookie := newCsrfCookie(&c.opt, c.token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
ctx.Resp.Header().Add("Set-Cookie", newCsrfCookie(&c.opt, c.token).String())
|
||||
}
|
||||
|
||||
ctx.Data["CsrfToken"] = c.token
|
||||
|
@ -58,6 +58,9 @@ func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) {
|
||||
func RequireRepoReader(unitType unit.Type) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.Repo.CanRead(unitType) {
|
||||
if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
|
||||
return
|
||||
}
|
||||
if log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+
|
||||
|
@ -374,7 +374,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() {
|
||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) {
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
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
|
||||
}
|
||||
|
@ -760,10 +760,15 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model
|
||||
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{
|
||||
RepoID: g.repo.ID,
|
||||
Repo: g.repo,
|
||||
Title: pr.Title,
|
||||
Title: prTitle,
|
||||
Index: pr.Number,
|
||||
Content: pr.Content,
|
||||
MilestoneID: milestoneID,
|
||||
|
@ -737,6 +737,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
|
||||
PatchURL: pr.GetPatchURL(), // see below for SECURITY related issues here
|
||||
Reactions: reactions,
|
||||
ForeignIndex: int64(*pr.Number),
|
||||
IsDraft: pr.GetDraft(),
|
||||
})
|
||||
|
||||
// SECURITY: Ensure that the PR is safe
|
||||
|
@ -722,6 +722,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque
|
||||
PatchURL: pr.WebURL + ".patch",
|
||||
ForeignIndex: int64(pr.IID),
|
||||
Context: gitlabIssueContext{IsMergeRequest: true},
|
||||
IsDraft: pr.Draft,
|
||||
})
|
||||
|
||||
// SECURITY: Ensure that the PR is safe
|
||||
|
@ -32,6 +32,10 @@ import (
|
||||
|
||||
// RenameUser renames a user
|
||||
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.
|
||||
if !u.IsOrganization() && !u.IsLocal() {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -114,12 +114,10 @@ func TestRenameUser(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 {
|
||||
t.Run(username, func(t *testing.T) {
|
||||
assert.Error(t, user_model.IsUsableUsername(username))
|
||||
assert.Error(t, RenameUser(db.DefaultContext, user, username))
|
||||
})
|
||||
assert.Error(t, user_model.IsUsableUsername(username), "non-usable username: %s", username)
|
||||
assert.Error(t, RenameUser(db.DefaultContext, user, username), "non-usable username: %s", username)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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{
|
||||
Action: api.HookIssueLabelCleared,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
})
|
||||
@ -150,7 +150,7 @@ func (m *webhookNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
|
||||
}
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}
|
||||
@ -201,7 +201,7 @@ func (m *webhookNotifier) IssueChangeTitle(ctx context.Context, doer *user_model
|
||||
From: oldTitle,
|
||||
},
|
||||
},
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
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.
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
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{
|
||||
Action: api.HookIssueOpened,
|
||||
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),
|
||||
Sender: convert.ToUser(ctx, pull.Issue.Poster, nil),
|
||||
}); err != nil {
|
||||
@ -336,7 +336,7 @@ func (m *webhookNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
|
||||
From: oldContent,
|
||||
},
|
||||
},
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
})
|
||||
@ -375,17 +375,20 @@ func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
|
||||
}
|
||||
|
||||
var eventType webhook_module.HookEventType
|
||||
var pullRequest *api.PullRequest
|
||||
if c.Issue.IsPull {
|
||||
eventType = webhook_module.HookEventPullRequestComment
|
||||
pullRequest = convert.ToAPIPullRequest(ctx, c.Issue.PullRequest, doer)
|
||||
} else {
|
||||
eventType = webhook_module.HookEventIssueComment
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, c.Issue.Repo, doer)
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
|
||||
Action: api.HookIssueCommentEdited,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, c.Issue),
|
||||
Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
|
||||
Action: api.HookIssueCommentEdited,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, c.Issue),
|
||||
PullRequest: pullRequest,
|
||||
Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
|
||||
Changes: &api.ChangesPayload{
|
||||
Body: &api.ChangesFromPayload{
|
||||
From: oldContent,
|
||||
@ -403,20 +406,23 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
|
||||
issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User,
|
||||
) {
|
||||
var eventType webhook_module.HookEventType
|
||||
var pullRequest *api.PullRequest
|
||||
if issue.IsPull {
|
||||
eventType = webhook_module.HookEventPullRequestComment
|
||||
pullRequest = convert.ToAPIPullRequest(ctx, issue.PullRequest, doer)
|
||||
} else {
|
||||
eventType = webhook_module.HookEventIssueComment
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
|
||||
Action: api.HookIssueCommentCreated,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, issue),
|
||||
Comment: convert.ToAPIComment(ctx, repo, comment),
|
||||
Repository: convert.ToRepo(ctx, repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
IsPull: issue.IsPull,
|
||||
Action: api.HookIssueCommentCreated,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, issue),
|
||||
PullRequest: pullRequest,
|
||||
Comment: convert.ToAPIComment(ctx, repo, comment),
|
||||
Repository: convert.ToRepo(ctx, repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
IsPull: issue.IsPull,
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
|
||||
}
|
||||
@ -440,20 +446,23 @@ func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
|
||||
}
|
||||
|
||||
var eventType webhook_module.HookEventType
|
||||
var pullRequest *api.PullRequest
|
||||
if comment.Issue.IsPull {
|
||||
eventType = webhook_module.HookEventPullRequestComment
|
||||
pullRequest = convert.ToAPIPullRequest(ctx, comment.Issue.PullRequest, doer)
|
||||
} else {
|
||||
eventType = webhook_module.HookEventIssueComment
|
||||
}
|
||||
|
||||
permission, _ := access_model.GetUserRepoPermission(ctx, comment.Issue.Repo, doer)
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
|
||||
Action: api.HookIssueCommentDeleted,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
|
||||
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
|
||||
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
IsPull: comment.Issue.IsPull,
|
||||
Action: api.HookIssueCommentDeleted,
|
||||
Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
|
||||
PullRequest: pullRequest,
|
||||
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
|
||||
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
IsPull: comment.Issue.IsPull,
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
|
||||
}
|
||||
@ -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{
|
||||
Action: api.HookIssueLabelUpdated,
|
||||
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}),
|
||||
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{
|
||||
Action: hookAction,
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
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.
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: pr.Issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, doer),
|
||||
Repository: convert.ToRepo(ctx, pr.Issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Action: api.HookIssueClosed,
|
||||
@ -668,7 +677,7 @@ func (m *webhookNotifier) PullRequestChangeTargetBranch(ctx context.Context, doe
|
||||
From: oldBranch,
|
||||
},
|
||||
},
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, doer),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, mode),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}); err != nil {
|
||||
@ -703,11 +712,12 @@ func (m *webhookNotifier) PullRequestReview(ctx context.Context, pr *issues_mode
|
||||
return
|
||||
}
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: review.Issue.Repo}, reviewHookType, &api.PullRequestPayload{
|
||||
Action: api.HookIssueReviewed,
|
||||
Index: review.Issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, nil),
|
||||
Repository: convert.ToRepo(ctx, review.Issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, review.Reviewer, nil),
|
||||
Action: api.HookIssueReviewed,
|
||||
Index: review.Issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, pr, review.Reviewer),
|
||||
RequestedReviewer: convert.ToUser(ctx, review.Reviewer, nil),
|
||||
Repository: convert.ToRepo(ctx, review.Issue.Repo, permission),
|
||||
Sender: convert.ToUser(ctx, review.Reviewer, nil),
|
||||
Review: &api.ReviewPayload{
|
||||
Type: string(reviewHookType),
|
||||
Content: review.Content,
|
||||
@ -729,7 +739,7 @@ func (m *webhookNotifier) PullRequestReviewRequest(ctx context.Context, doer *us
|
||||
}
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, nil),
|
||||
PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer),
|
||||
RequestedReviewer: convert.ToUser(ctx, reviewer, nil),
|
||||
Repository: convert.ToRepo(ctx, issue.Repo, permission),
|
||||
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{
|
||||
Action: api.HookIssueSynchronized,
|
||||
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}),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
}); err != nil {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "admin.auths.auth_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
||||
<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>
|
||||
</h4>
|
||||
<div class="ui attached table segment">
|
||||
@ -23,12 +23,12 @@
|
||||
{{range .Sources}}
|
||||
<tr>
|
||||
<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>{{svg (Iif .IsActive "octicon-check" "octicon-x")}}</td>
|
||||
<td>{{DateTime "short" .UpdatedUnix}}</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>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
@ -231,7 +231,7 @@
|
||||
<div class="divider"></div>
|
||||
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
|
||||
<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}}
|
||||
<div class="ui tiny input">
|
||||
<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>
|
||||
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.cache_test"}}</dt>
|
||||
<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}}
|
||||
<button class="ui tiny primary button">{{ctx.Locale.Tr "test"}}</button>
|
||||
</form>
|
||||
|
@ -24,7 +24,7 @@
|
||||
{{ctx.Locale.Tr "repository"}}
|
||||
</h4>
|
||||
<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">
|
||||
<details>
|
||||
<summary>{{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}}</summary>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{{ctx.Locale.Tr "admin.monitor.cron"}}
|
||||
</h4>
|
||||
<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">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -9,7 +9,7 @@
|
||||
{{ctx.Locale.Tr "admin.dashboard.maintenance_operations"}}
|
||||
</h4>
|
||||
<div class="ui attached table segment">
|
||||
<form method="post" action="{{AppSubUrl}}/admin">
|
||||
<form method="post" action="{{AppSubUrl}}/-/admin">
|
||||
{{.CsrfTokenHtml}}
|
||||
<table class="ui very basic table tw-mt-0 tw-px-4">
|
||||
<tbody>
|
||||
|
@ -80,7 +80,7 @@
|
||||
<div class="content">
|
||||
<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}}
|
||||
|
||||
<input type="hidden" id="query-sort" name="sort" value="{{.SortType}}">
|
||||
|
@ -5,10 +5,10 @@
|
||||
<details class="item toggleable-item" {{if or .PageIsAdminDashboard .PageIsAdminSelfCheck}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "admin.maintenance"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
@ -16,16 +16,16 @@
|
||||
<details class="item toggleable-item" {{if or .PageIsAdminUsers .PageIsAdminEmails .PageIsAdminOrganizations .PageIsAdminAuthentications}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "admin.identity_access"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
@ -34,11 +34,11 @@
|
||||
<summary>{{ctx.Locale.Tr "admin.assets"}}</summary>
|
||||
<div class="menu">
|
||||
{{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"}}
|
||||
</a>
|
||||
{{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"}}
|
||||
</a>
|
||||
</div>
|
||||
@ -48,22 +48,22 @@
|
||||
<details class="item toggleable-item" {{if or .PageIsAdminDefaultHooks .PageIsAdminSystemHooks .PageIsAdminApplications}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "admin.integrations"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
</details>
|
||||
{{else}}
|
||||
{{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"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{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"}}
|
||||
</a>
|
||||
{{end}}
|
||||
@ -72,10 +72,10 @@
|
||||
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsVariables}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
@ -84,30 +84,30 @@
|
||||
<details class="item toggleable-item" {{if or .PageIsAdminConfig}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "admin.config"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
</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"}}
|
||||
</a>
|
||||
<details class="item toggleable-item" {{if or .PageIsAdminMonitorStats .PageIsAdminMonitorCron .PageIsAdminMonitorQueue .PageIsAdminMonitorStacktrace}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "admin.monitor"}}</summary>
|
||||
<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"}}
|
||||
</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"}}
|
||||
</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"}}
|
||||
</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"}}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
||||
<tr>
|
||||
<th></th>
|
||||
<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}}
|
||||
<button type="submit" class="ui red small button">{{ctx.Locale.Tr "admin.notices.delete_all"}}</button>
|
||||
</form>
|
||||
|
@ -5,7 +5,7 @@
|
||||
{{ctx.Locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}},
|
||||
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}})
|
||||
<div class="ui right">
|
||||
<form method="post" action="{{AppSubUrl}}/admin/packages/cleanup">
|
||||
<form method="post" action="{{AppSubUrl}}/-/admin/packages/cleanup">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button>
|
||||
</form>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "admin.repos.repo_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
||||
<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>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "admin.repos.unadopted"}}
|
||||
<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>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
@ -31,7 +31,7 @@
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "repo.adopt_preexisting_content" $dir}}</p>
|
||||
</div>
|
||||
<form class="ui form" method="post" action="{{AppSubUrl}}/admin/repos/unadopted">
|
||||
<form class="ui form" method="post" action="{{AppSubUrl}}/-/admin/repos/unadopted">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="id" value="{{$dir}}">
|
||||
<input type="hidden" name="action" value="adopt">
|
||||
@ -48,7 +48,7 @@
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "repo.delete_preexisting_content" $dir}}</p>
|
||||
</div>
|
||||
<form class="ui form" method="post" action="{{AppSubUrl}}/admin/repos/unadopted">
|
||||
<form class="ui form" method="post" action="{{AppSubUrl}}/-/admin/repos/unadopted">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="id" value="{{$dir}}">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
|
@ -8,7 +8,7 @@
|
||||
<a class="{{if eq .ShowGoroutineList "stacktrace"}}active {{end}}item" href="?show=stacktrace">{{ctx.Locale.Tr "admin.monitor.stacktrace"}}</a>
|
||||
</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">
|
||||
<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"}}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "admin.users.user_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
||||
<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>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
@ -6,7 +6,7 @@
|
||||
{{if (or .ShowFooterVersion .PageIsAdmin)}}
|
||||
{{ctx.Locale.Tr "version"}}:
|
||||
{{if .IsAdmin}}
|
||||
<a href="{{AppSubUrl}}/admin/config">{{AppVer}}</a>
|
||||
<a href="{{AppSubUrl}}/-/admin/config">{{AppVer}}</a>
|
||||
{{else}}
|
||||
{{AppVer}}
|
||||
{{end}}
|
||||
|
@ -158,7 +158,7 @@
|
||||
{{if .IsAdmin}}
|
||||
<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"}}
|
||||
{{ctx.Locale.Tr "admin_panel"}}
|
||||
</a>
|
||||
|
@ -29,15 +29,16 @@
|
||||
<div class="default text">empty multiple dropdown</div>
|
||||
<div class="menu">
|
||||
<div class="item">item</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui multiple clearable search selection dropdown">
|
||||
<input type="hidden" value="1">
|
||||
{{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">sm1</div>
|
||||
<div class="item">sm2</div>
|
||||
<div class="item">medium1</div>
|
||||
<div class="item">medium2</div>
|
||||
<div class="item">large item1</div>
|
||||
<div class="item">large item2</div>
|
||||
<div class="item">large item3</div>
|
||||
<div class="item">very large item test 1</div>
|
||||
<div class="item">very large item test 2</div>
|
||||
<div class="item">very large item test 3</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui buttons">
|
||||
@ -50,6 +51,27 @@
|
||||
</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>
|
||||
<div>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</h4>
|
||||
<div class="ui attached guide table segment empty-repo-guide">
|
||||
<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">
|
||||
{{if and .CanWriteCode (not .Repository.IsArchived)}}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<div class="content tw-break-anywhere profile-avatar-name">
|
||||
{{if .ContextUser.FullName}}<span class="header text center">{{.ContextUser.FullName}}</span>{{end}}
|
||||
<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}}
|
||||
</a>
|
||||
{{end}}</span>
|
||||
|
25
templates/swagger/v1_json.tmpl
generated
25
templates/swagger/v1_json.tmpl
generated
@ -11209,26 +11209,27 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"description": "Owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"description": "Name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"closed",
|
||||
"open",
|
||||
"closed",
|
||||
"all"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "State of pull request: open or closed (optional)",
|
||||
"default": "open",
|
||||
"description": "State of pull request",
|
||||
"name": "state",
|
||||
"in": "query"
|
||||
},
|
||||
@ -11265,14 +11266,23 @@
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by pull request author",
|
||||
"name": "poster",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 1,
|
||||
"type": "integer",
|
||||
"description": "page number of results to return (1-based)",
|
||||
"default": 1,
|
||||
"description": "Page number of results to return (1-based)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"minimum": 0,
|
||||
"type": "integer",
|
||||
"description": "page size of results",
|
||||
"description": "Page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
}
|
||||
@ -11283,6 +11293,9 @@
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFound"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/error"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
@ -0,0 +1,4 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
@ -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
|
@ -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
|
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
|
7
tests/gitea-repositories-meta/org42/search-by-path.git/hooks/pre-receive
Executable file
7
tests/gitea-repositories-meta/org42/search-by-path.git/hooks/pre-receive
Executable 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
|
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
|
@ -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
|
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" proc-receive
|
7
tests/gitea-repositories-meta/org42/search-by-path.git/hooks/update
Executable file
7
tests/gitea-repositories-meta/org42/search-by-path.git/hooks/update
Executable 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/update.d"`; do
|
||||
sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3
|
||||
done
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user