diff options
Diffstat (limited to 'services/issue')
-rw-r--r-- | services/issue/comments.go | 40 | ||||
-rw-r--r-- | services/issue/commit.go | 18 | ||||
-rw-r--r-- | services/issue/issue.go | 13 | ||||
-rw-r--r-- | services/issue/milestone.go | 4 | ||||
-rw-r--r-- | services/issue/milestone_test.go | 8 | ||||
-rw-r--r-- | services/issue/status.go | 46 | ||||
-rw-r--r-- | services/issue/suggestion.go | 73 | ||||
-rw-r--r-- | services/issue/suggestion_test.go | 57 |
8 files changed, 230 insertions, 29 deletions
diff --git a/services/issue/comments.go b/services/issue/comments.go index 33b5702a00..46f92f7cd2 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -12,7 +12,10 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/timeutil" + git_service "code.gitea.io/gitea/services/git" notify_service "code.gitea.io/gitea/services/notify" ) @@ -139,3 +142,40 @@ func DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_m return nil } + +// LoadCommentPushCommits Load push commits +func LoadCommentPushCommits(ctx context.Context, c *issues_model.Comment) (err error) { + if c.Content == "" || c.Commits != nil || c.Type != issues_model.CommentTypePullRequestPush { + return nil + } + + var data issues_model.PushActionContent + err = json.Unmarshal([]byte(c.Content), &data) + if err != nil { + return err + } + + c.IsForcePush = data.IsForcePush + + if c.IsForcePush { + if len(data.CommitIDs) != 2 { + return nil + } + c.OldCommit = data.CommitIDs[0] + c.NewCommit = data.CommitIDs[1] + } else { + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo) + if err != nil { + return err + } + defer closer.Close() + + c.Commits, err = git_service.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) + if err != nil { + return err + } + c.CommitsNum = int64(len(c.Commits)) + } + + return err +} diff --git a/services/issue/commit.go b/services/issue/commit.go index 0579e0f5c5..963d0359fd 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -188,15 +188,19 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m continue } } - isClosed := ref.Action == references.XRefActionCloses - if isClosed && len(ref.TimeLog) > 0 { - if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil { + + refIssue.Repo = refRepo + if ref.Action == references.XRefActionCloses && !refIssue.IsClosed { + if len(ref.TimeLog) > 0 { + if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil { + return err + } + } + if err := CloseIssue(ctx, refIssue, doer, c.Sha1); err != nil { return err } - } - if isClosed != refIssue.IsClosed { - refIssue.Repo = refRepo - if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil { + } else if ref.Action == references.XRefActionReopens && refIssue.IsClosed { + if err := ReopenIssue(ctx, refIssue, doer, c.Sha1); err != nil { return err } } diff --git a/services/issue/issue.go b/services/issue/issue.go index c6a52cc0fe..586b6031c8 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -197,13 +197,6 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi } } - // If the Issue is pinned, we should unpin it before deletion to avoid problems with other pinned Issues - if issue.IsPinned() { - if err := issue.Unpin(ctx, doer); err != nil { - return err - } - } - notify_service.DeleteIssue(ctx, doer, issue) return nil @@ -250,8 +243,9 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i issueRefURLs := make(map[int64]string, len(issues)) for _, issue := range issues { if issue.Ref != "" { - issueRefEndNames[issue.ID] = git.RefName(issue.Ref).ShortName() - issueRefURLs[issue.ID] = git.RefURL(repoLink, issue.Ref) + ref := git.RefName(issue.Ref) + issueRefEndNames[issue.ID] = ref.ShortName() + issueRefURLs[issue.ID] = repoLink + "/src/" + ref.RefWebLinkPath() } } return issueRefEndNames, issueRefURLs @@ -318,6 +312,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error { &issues_model.Comment{RefIssueID: issue.ID}, &issues_model.IssueDependency{DependencyID: issue.ID}, &issues_model.Comment{DependentIssueID: issue.ID}, + &issues_model.IssuePin{IssueID: issue.ID}, ); err != nil { return err } diff --git a/services/issue/milestone.go b/services/issue/milestone.go index ff645744a7..beb6f131a9 100644 --- a/services/issue/milestone.go +++ b/services/issue/milestone.go @@ -59,6 +59,10 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is } } + if issue.MilestoneID == 0 { + issue.Milestone = nil + } + return nil } diff --git a/services/issue/milestone_test.go b/services/issue/milestone_test.go index 42b910166f..bf5abc85b7 100644 --- a/services/issue/milestone_test.go +++ b/services/issue/milestone_test.go @@ -23,6 +23,7 @@ func TestChangeMilestoneAssign(t *testing.T) { oldMilestoneID := issue.MilestoneID issue.MilestoneID = 2 + assert.NoError(t, issue.LoadMilestone(db.DefaultContext)) assert.NoError(t, ChangeMilestoneAssign(db.DefaultContext, issue, doer, oldMilestoneID)) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ IssueID: issue.ID, @@ -31,4 +32,11 @@ func TestChangeMilestoneAssign(t *testing.T) { OldMilestoneID: oldMilestoneID, }) unittest.CheckConsistencyFor(t, &issues_model.Milestone{}, &issues_model.Issue{}) + assert.NotNil(t, issue.Milestone) + + oldMilestoneID = issue.MilestoneID + issue.MilestoneID = 0 + assert.NoError(t, ChangeMilestoneAssign(db.DefaultContext, issue, doer, oldMilestoneID)) + assert.EqualValues(t, 0, issue.MilestoneID) + assert.Nil(t, issue.Milestone) } diff --git a/services/issue/status.go b/services/issue/status.go index 967c29bd22..e18b891175 100644 --- a/services/issue/status.go +++ b/services/issue/status.go @@ -6,34 +6,54 @@ package issue import ( "context" + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" notify_service "code.gitea.io/gitea/services/notify" ) -// ChangeStatus changes issue status to open or closed. -// closed means the target status -// Fix me: you should check whether the current issue status is same to the target status before call this function -// as in function changeIssueStatus we will return WasClosedError, even the issue status and target status are both open -func ChangeStatus(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string, closed bool) error { - comment, err := issues_model.ChangeIssueStatus(ctx, issue, doer, closed) +// CloseIssue close an issue. +func CloseIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error { + dbCtx, committer, err := db.TxContext(ctx) if err != nil { - if issues_model.IsErrDependenciesLeft(err) && closed { - if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil { + return err + } + defer committer.Close() + + comment, err := issues_model.CloseIssue(dbCtx, issue, doer) + if err != nil { + if issues_model.IsErrDependenciesLeft(err) { + if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil { log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err) } } return err } - if closed { - if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil { - return err - } + if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil { + return err + } + + if err := committer.Commit(); err != nil { + return err + } + committer.Close() + + notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, true) + + return nil +} + +// ReopenIssue reopen an issue. +// FIXME: If some issues dependent this one are closed, should we also reopen them? +func ReopenIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error { + comment, err := issues_model.ReopenIssue(ctx, issue, doer) + if err != nil { + return err } - notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, closed) + notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, false) return nil } diff --git a/services/issue/suggestion.go b/services/issue/suggestion.go new file mode 100644 index 0000000000..22eddb1904 --- /dev/null +++ b/services/issue/suggestion.go @@ -0,0 +1,73 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "context" + "strconv" + + issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/structs" +) + +func GetSuggestion(ctx context.Context, repo *repo_model.Repository, isPull optional.Option[bool], keyword string) ([]*structs.Issue, error) { + var issues issues_model.IssueList + var err error + pageSize := 5 + if keyword == "" { + issues, err = issues_model.FindLatestUpdatedIssues(ctx, repo.ID, isPull, pageSize) + if err != nil { + return nil, err + } + } else { + indexKeyword, _ := strconv.ParseInt(keyword, 10, 64) + var issueByIndex *issues_model.Issue + var excludedID int64 + if indexKeyword > 0 { + issueByIndex, err = issues_model.GetIssueByIndex(ctx, repo.ID, indexKeyword) + if err != nil && !issues_model.IsErrIssueNotExist(err) { + return nil, err + } + if issueByIndex != nil { + excludedID = issueByIndex.ID + pageSize-- + } + } + + issues, err = issues_model.FindIssuesSuggestionByKeyword(ctx, repo.ID, keyword, isPull, excludedID, pageSize) + if err != nil { + return nil, err + } + + if issueByIndex != nil { + issues = append([]*issues_model.Issue{issueByIndex}, issues...) + } + } + + if err := issues.LoadPullRequests(ctx); err != nil { + return nil, err + } + + suggestions := make([]*structs.Issue, 0, len(issues)) + for _, issue := range issues { + suggestion := &structs.Issue{ + ID: issue.ID, + Index: issue.Index, + Title: issue.Title, + State: issue.State(), + } + + if issue.IsPull && issue.PullRequest != nil { + suggestion.PullRequest = &structs.PullRequestMeta{ + HasMerged: issue.PullRequest.HasMerged, + IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx), + } + } + suggestions = append(suggestions, suggestion) + } + + return suggestions, nil +} diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go new file mode 100644 index 0000000000..84cfd520ac --- /dev/null +++ b/services/issue/suggestion_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issue + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/optional" + + "github.com/stretchr/testify/assert" +) + +func Test_Suggestion(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + testCases := []struct { + keyword string + isPull optional.Option[bool] + expectedIndexes []int64 + }{ + { + keyword: "", + expectedIndexes: []int64{5, 1, 4, 2, 3}, + }, + { + keyword: "1", + expectedIndexes: []int64{1}, + }, + { + keyword: "issue", + expectedIndexes: []int64{4, 1, 2, 3}, + }, + { + keyword: "pull", + expectedIndexes: []int64{5}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.keyword, func(t *testing.T) { + issues, err := GetSuggestion(db.DefaultContext, repo1, testCase.isPull, testCase.keyword) + assert.NoError(t, err) + + issueIndexes := make([]int64, 0, len(issues)) + for _, issue := range issues { + issueIndexes = append(issueIndexes, issue.Index) + } + assert.EqualValues(t, testCase.expectedIndexes, issueIndexes) + }) + } +} |