mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into rellist
This commit is contained in:
commit
15f3c1712c
|
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateIssueByAPI updates all allowed fields of given issue.
|
|
||||||
// If the issue status is changed a statusChangeComment is returned
|
|
||||||
// similarly if the title is changed the titleChanged bool is set to true
|
|
||||||
func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err := issue.LoadRepo(ctx); err != nil {
|
|
||||||
return nil, false, fmt.Errorf("loadRepo: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the issue
|
|
||||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
|
|
||||||
"name", "content", "milestone_id", "priority",
|
|
||||||
"deadline_unix", "updated_unix", "is_locked").
|
|
||||||
Update(issue); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
titleChanged = currentIssue.Title != issue.Title
|
|
||||||
if titleChanged {
|
|
||||||
opts := &CreateCommentOptions{
|
|
||||||
Type: CommentTypeChangeTitle,
|
|
||||||
Doer: doer,
|
|
||||||
Repo: issue.Repo,
|
|
||||||
Issue: issue,
|
|
||||||
OldTitle: currentIssue.Title,
|
|
||||||
NewTitle: issue.Title,
|
|
||||||
}
|
|
||||||
_, err := CreateComment(ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("createComment: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentIssue.IsClosed != issue.IsClosed {
|
|
||||||
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
return statusChangeComment, titleChanged, committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
||||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||||
// if the deadline hasn't changed do nothing
|
// if the deadline hasn't changed do nothing
|
||||||
|
|
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
||||||
ContextLineNumber int
|
ContextLineNumber int
|
||||||
IsFuzzy bool
|
IsFuzzy bool
|
||||||
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
||||||
|
PathspecList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
||||||
|
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||||
}
|
}
|
||||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||||
|
cmd.AddDashesAndList(opts.PathspecList...)
|
||||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||||
stderr := bytes.Buffer{}
|
stderr := bytes.Buffer{}
|
||||||
err = cmd.Run(&RunOpts{
|
err = cmd.Run(&RunOpts{
|
||||||
|
|
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, res)
|
}, res)
|
||||||
|
|
||||||
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []*GrepResult{
|
||||||
|
{
|
||||||
|
Filename: "java-hello/main.java",
|
||||||
|
LineNumbers: []int{3},
|
||||||
|
LineCodes: []string{" public static void main(String[] args)"},
|
||||||
|
},
|
||||||
|
}, res)
|
||||||
|
|
||||||
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []*GrepResult{
|
||||||
|
{
|
||||||
|
Filename: "main.vendor.java",
|
||||||
|
LineNumbers: []int{3},
|
||||||
|
LineCodes: []string{" public static void main(String[] args)"},
|
||||||
|
},
|
||||||
|
}, res)
|
||||||
|
|
||||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, []*GrepResult{
|
assert.Equal(t, []*GrepResult{
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import "github.com/gobwas/glob"
|
||||||
|
|
||||||
|
type GlobMatcher struct {
|
||||||
|
compiledGlob glob.Glob
|
||||||
|
patternString string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ glob.Glob = (*GlobMatcher)(nil)
|
||||||
|
|
||||||
|
func (g *GlobMatcher) Match(s string) bool {
|
||||||
|
return g.compiledGlob.Match(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlobMatcher) PatternString() string {
|
||||||
|
return g.patternString
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobMatcherCompile(pattern string, separators ...rune) (*GlobMatcher, error) {
|
||||||
|
g, err := glob.Compile(pattern, separators...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &GlobMatcher{
|
||||||
|
compiledGlob: g,
|
||||||
|
patternString: pattern,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Indexer settings
|
// Indexer settings
|
||||||
|
@ -30,8 +28,8 @@ var Indexer = struct {
|
||||||
RepoConnStr string
|
RepoConnStr string
|
||||||
RepoIndexerName string
|
RepoIndexerName string
|
||||||
MaxIndexerFileSize int64
|
MaxIndexerFileSize int64
|
||||||
IncludePatterns []glob.Glob
|
IncludePatterns []*GlobMatcher
|
||||||
ExcludePatterns []glob.Glob
|
ExcludePatterns []*GlobMatcher
|
||||||
ExcludeVendored bool
|
ExcludeVendored bool
|
||||||
}{
|
}{
|
||||||
IssueType: "bleve",
|
IssueType: "bleve",
|
||||||
|
@ -93,12 +91,12 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
|
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
|
||||||
func IndexerGlobFromString(globstr string) []glob.Glob {
|
func IndexerGlobFromString(globstr string) []*GlobMatcher {
|
||||||
extarr := make([]glob.Glob, 0, 10)
|
extarr := make([]*GlobMatcher, 0, 10)
|
||||||
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
||||||
expr = strings.TrimSpace(expr)
|
expr = strings.TrimSpace(expr)
|
||||||
if expr != "" {
|
if expr != "" {
|
||||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
|
||||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||||
} else {
|
} else {
|
||||||
extarr = append(extarr, g)
|
extarr = append(extarr, g)
|
||||||
|
|
|
@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
|
||||||
// EditPullRequestOption options when modify pull request
|
// EditPullRequestOption options when modify pull request
|
||||||
type EditPullRequestOption struct {
|
type EditPullRequestOption struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body *string `json:"body"`
|
||||||
Base string `json:"base"`
|
Base string `json:"base"`
|
||||||
Assignee string `json:"assignee"`
|
Assignee string `json:"assignee"`
|
||||||
Assignees []string `json:"assignees"`
|
Assignees []string `json:"assignees"`
|
||||||
|
|
|
@ -140,9 +140,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
||||||
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
||||||
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
||||||
|
|
||||||
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
|
||||||
log.Error("write bytes failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SearchIssues searches for issues across the repositories that the user has access to
|
// SearchIssues searches for issues across the repositories that the user has access to
|
||||||
|
@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
|
||||||
if len(form.Title) > 0 {
|
if len(form.Title) > 0 {
|
||||||
issue.Title = form.Title
|
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if form.Body != nil {
|
if form.Body != nil {
|
||||||
issue.Content = *form.Body
|
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if form.Ref != nil {
|
if form.Ref != nil {
|
||||||
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
||||||
|
@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||||
}
|
if issues_model.IsErrDependenciesLeft(err) {
|
||||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||||
if err != nil {
|
return
|
||||||
if issues_model.IsErrDependenciesLeft(err) {
|
}
|
||||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if titleChanged {
|
|
||||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusChangeComment != nil {
|
|
||||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refetch from database to assign some automatic values
|
// Refetch from database to assign some automatic values
|
||||||
|
|
|
@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldTitle := issue.Title
|
|
||||||
if len(form.Title) > 0 {
|
if len(form.Title) > 0 {
|
||||||
issue.Title = form.Title
|
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(form.Body) > 0 {
|
if form.Body != nil {
|
||||||
issue.Content = form.Body
|
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update or remove deadline if set
|
// Update or remove deadline if set
|
||||||
|
@ -686,24 +693,14 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||||
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
|
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||||
}
|
if issues_model.IsErrDependenciesLeft(err) {
|
||||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||||
if err != nil {
|
return
|
||||||
if issues_model.IsErrDependenciesLeft(err) {
|
}
|
||||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if titleChanged {
|
|
||||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusChangeComment != nil {
|
|
||||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// change pull target branch
|
// change pull target branch
|
||||||
|
|
|
@ -6,10 +6,8 @@ package user
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
@ -44,7 +42,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
|
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAnyUnitAccess() {
|
||||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,16 @@ import (
|
||||||
|
|
||||||
const tplSearch base.TplName = "repo/search"
|
const tplSearch base.TplName = "repo/search"
|
||||||
|
|
||||||
|
func indexSettingToGitGrepPathspecList() (list []string) {
|
||||||
|
for _, expr := range setting.Indexer.IncludePatterns {
|
||||||
|
list = append(list, ":(glob)"+expr.PatternString())
|
||||||
|
}
|
||||||
|
for _, expr := range setting.Indexer.ExcludePatterns {
|
||||||
|
list = append(list, ":(glob,exclude)"+expr.PatternString())
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
// Search render repository search page
|
// Search render repository search page
|
||||||
func Search(ctx *context.Context) {
|
func Search(ctx *context.Context) {
|
||||||
language := ctx.FormTrim("l")
|
language := ctx.FormTrim("l")
|
||||||
|
@ -65,8 +75,14 @@ func Search(ctx *context.Context) {
|
||||||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ContextLineNumber: 3, IsFuzzy: isFuzzy})
|
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{
|
||||||
|
ContextLineNumber: 1,
|
||||||
|
IsFuzzy: isFuzzy,
|
||||||
|
RefName: git.RefNameFromBranch(ctx.Repo.BranchName).String(), // BranchName should be default branch or the first existing branch
|
||||||
|
PathspecList: indexSettingToGitGrepPathspecList(),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
|
||||||
ctx.ServerError("GrepSearch", err)
|
ctx.ServerError("GrepSearch", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIndexSettingToGitGrepPathspecList(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("a"))()
|
||||||
|
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("b"))()
|
||||||
|
assert.Equal(t, []string{":(glob)a", ":(glob,exclude)b"}, indexSettingToGitGrepPathspecList())
|
||||||
|
}
|
|
@ -234,9 +234,7 @@ func (b *Base) plainTextInternal(skip, status int, bs []byte) {
|
||||||
b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||||
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
b.Resp.WriteHeader(status)
|
b.Resp.WriteHeader(status)
|
||||||
if _, err := b.Resp.Write(bs); err != nil {
|
_, _ = b.Resp.Write(bs)
|
||||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlainTextBytes renders bytes as plain text
|
// PlainTextBytes renders bytes as plain text
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -77,7 +78,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
|
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
|
||||||
if err == nil {
|
if err == nil || errors.Is(err, syscall.EPIPE) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
SSH
|
SSH
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
<input id="repo-clone-url" size="20" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
||||||
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
|
<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
|
||||||
{{svg "octicon-copy" 14}}
|
{{svg "octicon-copy" 14}}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
{{$l := Eval $n "-" 1}}
|
{{$l := Eval $n "-" 1}}
|
||||||
{{$isHomepage := (eq $n 0)}}
|
{{$isHomepage := (eq $n 0)}}
|
||||||
<div class="repo-button-row">
|
<div class="repo-button-row">
|
||||||
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-y-2">
|
<div class="repo-button-row-left">
|
||||||
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
{{template "repo/branch_dropdown" dict "root" . "ContainerClasses" "tw-mr-1"}}
|
||||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||||
{{$cmpBranch := ""}}
|
{{$cmpBranch := ""}}
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
|
||||||
<button class="ui dropdown basic compact jump button tw-mr-1"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
|
||||||
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
{{ctx.Locale.Tr "repo.editor.add_file"}}
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
@ -93,9 +93,9 @@
|
||||||
{{if $isHomepage}}
|
{{if $isHomepage}}
|
||||||
{{/* only show the "code search" on the repo home page, it only does global search,
|
{{/* only show the "code search" on the repo home page, it only does global search,
|
||||||
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
|
||||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
<form class="ignore-dirty tw-flex tw-flex-1" action="{{.RepoLink}}/search" method="get">
|
||||||
<div class="ui small action input">
|
<div class="ui small action input tw-flex-1">
|
||||||
<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||||
{{template "shared/search/button"}}
|
{{template "shared/search/button"}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -113,7 +113,7 @@
|
||||||
</span>
|
</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="repo-button-row-right">
|
||||||
<!-- Only show clone panel in repository home page -->
|
<!-- Only show clone panel in repository home page -->
|
||||||
{{if $isHomepage}}
|
{{if $isHomepage}}
|
||||||
<div class="clone-panel ui action tiny input">
|
<div class="clone-panel ui action tiny input">
|
||||||
|
|
|
@ -194,6 +194,10 @@ func TestAPIEditIssue(t *testing.T) {
|
||||||
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
|
||||||
repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
|
||||||
|
|
||||||
|
// check comment history
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issueAfter.ID, OldTitle: issueBefore.Title, NewTitle: title})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: issueAfter.ID, ContentText: body, IsFirstCreated: false})
|
||||||
|
|
||||||
// check deleted user
|
// check deleted user
|
||||||
assert.Equal(t, int64(500), issueAfter.PosterID)
|
assert.Equal(t, int64(500), issueAfter.PosterID)
|
||||||
assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
|
assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
|
||||||
|
|
|
@ -223,23 +223,33 @@ func TestAPIEditPull(t *testing.T) {
|
||||||
|
|
||||||
session := loginUser(t, owner10.Name)
|
session := loginUser(t, owner10.Name)
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
title := "create a success pr"
|
||||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
|
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
|
||||||
Head: "develop",
|
Head: "develop",
|
||||||
Base: "master",
|
Base: "master",
|
||||||
Title: "create a success pr",
|
Title: title,
|
||||||
}).AddTokenAuth(token)
|
}).AddTokenAuth(token)
|
||||||
pull := new(api.PullRequest)
|
apiPull := new(api.PullRequest)
|
||||||
resp := MakeRequest(t, req, http.StatusCreated)
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
DecodeJSON(t, resp, pull)
|
DecodeJSON(t, resp, apiPull)
|
||||||
assert.EqualValues(t, "master", pull.Base.Name)
|
assert.EqualValues(t, "master", apiPull.Base.Name)
|
||||||
|
|
||||||
req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{
|
newTitle := "edit a this pr"
|
||||||
|
newBody := "edited body"
|
||||||
|
req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index), &api.EditPullRequestOption{
|
||||||
Base: "feature/1",
|
Base: "feature/1",
|
||||||
Title: "edit a this pr",
|
Title: newTitle,
|
||||||
|
Body: &newBody,
|
||||||
}).AddTokenAuth(token)
|
}).AddTokenAuth(token)
|
||||||
resp = MakeRequest(t, req, http.StatusCreated)
|
resp = MakeRequest(t, req, http.StatusCreated)
|
||||||
DecodeJSON(t, resp, pull)
|
DecodeJSON(t, resp, apiPull)
|
||||||
assert.EqualValues(t, "feature/1", pull.Base.Name)
|
assert.EqualValues(t, "feature/1", apiPull.Base.Name)
|
||||||
|
// check comment history
|
||||||
|
pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
|
||||||
|
err := pull.LoadIssue(db.DefaultContext)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle})
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false})
|
||||||
|
|
||||||
req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{
|
req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{
|
||||||
Base: "not-exist",
|
Base: "not-exist",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -326,6 +327,39 @@ func TestAPIOrgRepos(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories.
|
||||||
|
func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"})
|
||||||
|
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID})
|
||||||
|
|
||||||
|
// Disable code repository unit.
|
||||||
|
var units []unit_model.Type
|
||||||
|
units = append(units, unit_model.TypeCode)
|
||||||
|
|
||||||
|
if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil {
|
||||||
|
assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err)
|
||||||
|
}
|
||||||
|
assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode))
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
|
||||||
|
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var apiRepos []*api.Repository
|
||||||
|
DecodeJSON(t, resp, &apiRepos)
|
||||||
|
|
||||||
|
var repoNames []string
|
||||||
|
for _, r := range apiRepos {
|
||||||
|
repoNames = append(repoNames, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, repoNames, repo21.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
|
func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
|
|
@ -188,8 +188,8 @@
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
.ui.action.input:not([class*="left action"]) > input:focus + .button,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
.ui.action.input:not([class*="left action"]) > input:focus + .button:hover,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button,
|
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button,
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover {
|
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button:hover {
|
||||||
border-left-color: var(--color-primary);
|
border-left-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||||
|
|
|
@ -128,15 +128,22 @@
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository .clone-panel #repo-clone-url {
|
.repository .clone-panel {
|
||||||
width: 320px;
|
display: flex;
|
||||||
border-radius: 0;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
.repository.wiki .clone-panel {
|
||||||
.repository .clone-panel #repo-clone-url {
|
flex: 0;
|
||||||
width: 200px;
|
}
|
||||||
}
|
|
||||||
|
.repository.wiki .clone-panel input {
|
||||||
|
width: 20ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repository .clone-panel #repo-clone-url {
|
||||||
|
border-radius: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository .ui.action.input.clone-panel > button + button,
|
.repository .ui.action.input.clone-panel > button + button,
|
||||||
|
@ -2229,17 +2236,37 @@ td .commit-summary {
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row {
|
.repo-button-row {
|
||||||
margin: 10px 0;
|
margin: 8px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repo-button-row-left,
|
||||||
|
.repo-button-row-right {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-button-row-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.repository:not(.wiki) .repo-button-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.repo-button-row .button {
|
.repo-button-row .button {
|
||||||
padding: 6px 10px !important;
|
padding: 6px 10px !important;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row .button.dropdown:not(.icon) {
|
.repo-button-row .button.dropdown:not(.icon) {
|
||||||
|
@ -2250,6 +2277,12 @@ td .commit-summary {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.repo-button-row-left {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tbody.commit-list {
|
tbody.commit-list {
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ export function initRepoCommentForm() {
|
||||||
|
|
||||||
function initBranchSelector() {
|
function initBranchSelector() {
|
||||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||||
|
if (!elSelectBranch) return;
|
||||||
const isForNewIssue = elSelectBranch.getAttribute('data-for-new-issue') === 'true';
|
const isForNewIssue = elSelectBranch.getAttribute('data-for-new-issue') === 'true';
|
||||||
|
|
||||||
const $selectBranch = $(elSelectBranch);
|
const $selectBranch = $(elSelectBranch);
|
||||||
|
|
|
@ -3,11 +3,12 @@ import {queryElemChildren} from '../../utils/dom.js';
|
||||||
|
|
||||||
export function initFomanticDimmer() {
|
export function initFomanticDimmer() {
|
||||||
// stand-in for removed dimmer module
|
// stand-in for removed dimmer module
|
||||||
$.fn.dimmer = function (arg0, $el) {
|
$.fn.dimmer = function (arg0, arg1) {
|
||||||
if (arg0 === 'add content') {
|
if (arg0 === 'add content') {
|
||||||
|
const $el = arg1;
|
||||||
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
||||||
if (existingDimmer) {
|
if (existingDimmer) {
|
||||||
queryElemChildren(existingDimmer, '*', (el) => el.remove());
|
queryElemChildren(existingDimmer, '*', (el) => el.classList.add('hidden'));
|
||||||
this._dimmer = existingDimmer;
|
this._dimmer = existingDimmer;
|
||||||
} else {
|
} else {
|
||||||
this._dimmer = document.createElement('div');
|
this._dimmer = document.createElement('div');
|
||||||
|
@ -21,8 +22,10 @@ export function initFomanticDimmer() {
|
||||||
this._dimmer.classList.add('active');
|
this._dimmer.classList.add('active');
|
||||||
document.body.classList.add('tw-overflow-hidden');
|
document.body.classList.add('tw-overflow-hidden');
|
||||||
} else if (arg0 === 'hide') {
|
} else if (arg0 === 'hide') {
|
||||||
|
const cb = arg1;
|
||||||
this._dimmer.classList.remove('active');
|
this._dimmer.classList.remove('active');
|
||||||
document.body.classList.remove('tw-overflow-hidden');
|
document.body.classList.remove('tw-overflow-hidden');
|
||||||
|
cb();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue