mirror of
https://github.com/go-gitea/gitea
synced 2024-10-20 04:19:50 +02:00
Compare commits
8 Commits
1203e7cd80
...
15f3c1712c
Author | SHA1 | Date | |
---|---|---|---|
|
15f3c1712c | ||
|
0f3e717a1a | ||
|
9f0ef3621a | ||
|
a50026e2f3 | ||
|
53b55223d1 | ||
|
c4e875402b | ||
|
b30b7df9f4 | ||
|
c445a85528 |
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
||||
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.
|
||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||
// if the deadline hasn't changed do nothing
|
||||
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
||||
ContextLineNumber int
|
||||
IsFuzzy bool
|
||||
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) {
|
||||
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
||||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||
}
|
||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||
cmd.AddDashesAndList(opts.PathspecList...)
|
||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||
stderr := bytes.Buffer{}
|
||||
err = cmd.Run(&RunOpts{
|
||||
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
||||
},
|
||||
}, 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})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
|
32
modules/setting/glob.go
Normal file
32
modules/setting/glob.go
Normal file
@ -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"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// Indexer settings
|
||||
@ -30,8 +28,8 @@ var Indexer = struct {
|
||||
RepoConnStr string
|
||||
RepoIndexerName string
|
||||
MaxIndexerFileSize int64
|
||||
IncludePatterns []glob.Glob
|
||||
ExcludePatterns []glob.Glob
|
||||
IncludePatterns []*GlobMatcher
|
||||
ExcludePatterns []*GlobMatcher
|
||||
ExcludeVendored bool
|
||||
}{
|
||||
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
|
||||
func IndexerGlobFromString(globstr string) []glob.Glob {
|
||||
extarr := make([]glob.Glob, 0, 10)
|
||||
func IndexerGlobFromString(globstr string) []*GlobMatcher {
|
||||
extarr := make([]*GlobMatcher, 0, 10)
|
||||
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
||||
expr = strings.TrimSpace(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)
|
||||
} else {
|
||||
extarr = append(extarr, g)
|
||||
|
@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
|
||||
// EditPullRequestOption options when modify pull request
|
||||
type EditPullRequestOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Body *string `json:"body"`
|
||||
Base string `json:"base"`
|
||||
Assignee string `json:"assignee"`
|
||||
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-Type", contentTypeXML)
|
||||
|
||||
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
||||
log.Error("write bytes failed: %v", err)
|
||||
}
|
||||
_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
|
||||
}
|
||||
|
||||
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/convert"
|
||||
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
|
||||
@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
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 {
|
||||
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 {
|
||||
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
||||
@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
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
|
||||
|
@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
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 {
|
||||
issue.Content = form.Body
|
||||
if form.Body != nil {
|
||||
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
|
||||
@ -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")
|
||||
return
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
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
|
||||
|
@ -6,10 +6,8 @@ package user
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"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)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,16 @@ import (
|
||||
|
||||
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
|
||||
func Search(ctx *context.Context) {
|
||||
language := ctx.FormTrim("l")
|
||||
@ -65,8 +75,14 @@ func Search(ctx *context.Context) {
|
||||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||
}
|
||||
} 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 {
|
||||
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
|
||||
ctx.ServerError("GrepSearch", err)
|
||||
return
|
||||
}
|
||||
|
19
routers/web/repo/search_test.go
Normal file
19
routers/web/repo/search_test.go
Normal file
@ -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("X-Content-Type-Options", "nosniff")
|
||||
b.Resp.WriteHeader(status)
|
||||
if _, err := b.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
_, _ = b.Resp.Write(bs)
|
||||
}
|
||||
|
||||
// PlainTextBytes renders bytes as plain text
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
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)
|
||||
if err == nil {
|
||||
if err == nil || errors.Is(err, syscall.EPIPE) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
SSH
|
||||
</button>
|
||||
{{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"}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</button>
|
||||
|
@ -46,7 +46,7 @@
|
||||
{{$l := Eval $n "-" 1}}
|
||||
{{$isHomepage := (eq $n 0)}}
|
||||
<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"}}
|
||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||
{{$cmpBranch := ""}}
|
||||
@ -66,7 +66,7 @@
|
||||
{{end}}
|
||||
|
||||
{{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"}}
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
@ -93,9 +93,9 @@
|
||||
{{if $isHomepage}}
|
||||
{{/* 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) */}}
|
||||
<form class="ignore-dirty" action="{{.RepoLink}}/search" method="get">
|
||||
<div class="ui small action input">
|
||||
<input name="q" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||
<form class="ignore-dirty tw-flex tw-flex-1" action="{{.RepoLink}}/search" method="get">
|
||||
<div class="ui small action input tw-flex-1">
|
||||
<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
</form>
|
||||
@ -113,7 +113,7 @@
|
||||
</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<div class="repo-button-row-right">
|
||||
<!-- Only show clone panel in repository home page -->
|
||||
{{if $isHomepage}}
|
||||
<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})
|
||||
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
|
||||
assert.Equal(t, int64(500), issueAfter.PosterID)
|
||||
assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
|
||||
|
@ -223,23 +223,33 @@ func TestAPIEditPull(t *testing.T) {
|
||||
|
||||
session := loginUser(t, owner10.Name)
|
||||
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{
|
||||
Head: "develop",
|
||||
Base: "master",
|
||||
Title: "create a success pr",
|
||||
Title: title,
|
||||
}).AddTokenAuth(token)
|
||||
pull := new(api.PullRequest)
|
||||
apiPull := new(api.PullRequest)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, pull)
|
||||
assert.EqualValues(t, "master", pull.Base.Name)
|
||||
DecodeJSON(t, resp, apiPull)
|
||||
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",
|
||||
Title: "edit a this pr",
|
||||
Title: newTitle,
|
||||
Body: &newBody,
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusCreated)
|
||||
DecodeJSON(t, resp, pull)
|
||||
assert.EqualValues(t, "feature/1", pull.Base.Name)
|
||||
DecodeJSON(t, resp, apiPull)
|
||||
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{
|
||||
Base: "not-exist",
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"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) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
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 + .button,
|
||||
.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 + .icon + .button:hover {
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button,
|
||||
.ui.action.input:not([class*="left action"]) > input:focus + i.icon + .button:hover {
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
.ui.action.input:not([class*="left action"]) > input:focus {
|
||||
|
@ -128,15 +128,22 @@
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.repository .clone-panel #repo-clone-url {
|
||||
width: 320px;
|
||||
border-radius: 0;
|
||||
.repository .clone-panel {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.repository .clone-panel #repo-clone-url {
|
||||
width: 200px;
|
||||
}
|
||||
.repository.wiki .clone-panel {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.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,
|
||||
@ -2229,17 +2236,37 @@ td .commit-summary {
|
||||
}
|
||||
|
||||
.repo-button-row {
|
||||
margin: 10px 0;
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
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 {
|
||||
padding: 6px 10px !important;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.repo-button-row .button.dropdown:not(.icon) {
|
||||
@ -2250,6 +2277,12 @@ td .commit-summary {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.repo-button-row-left {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
tbody.commit-list {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ export function initRepoCommentForm() {
|
||||
|
||||
function initBranchSelector() {
|
||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||
if (!elSelectBranch) return;
|
||||
const isForNewIssue = elSelectBranch.getAttribute('data-for-new-issue') === 'true';
|
||||
|
||||
const $selectBranch = $(elSelectBranch);
|
||||
|
@ -3,11 +3,12 @@ import {queryElemChildren} from '../../utils/dom.js';
|
||||
|
||||
export function initFomanticDimmer() {
|
||||
// stand-in for removed dimmer module
|
||||
$.fn.dimmer = function (arg0, $el) {
|
||||
$.fn.dimmer = function (arg0, arg1) {
|
||||
if (arg0 === 'add content') {
|
||||
const $el = arg1;
|
||||
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
||||
if (existingDimmer) {
|
||||
queryElemChildren(existingDimmer, '*', (el) => el.remove());
|
||||
queryElemChildren(existingDimmer, '*', (el) => el.classList.add('hidden'));
|
||||
this._dimmer = existingDimmer;
|
||||
} else {
|
||||
this._dimmer = document.createElement('div');
|
||||
@ -21,8 +22,10 @@ export function initFomanticDimmer() {
|
||||
this._dimmer.classList.add('active');
|
||||
document.body.classList.add('tw-overflow-hidden');
|
||||
} else if (arg0 === 'hide') {
|
||||
const cb = arg1;
|
||||
this._dimmer.classList.remove('active');
|
||||
document.body.classList.remove('tw-overflow-hidden');
|
||||
cb();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user