aboutsummaryrefslogtreecommitdiffstats
path: root/services/issue
diff options
context:
space:
mode:
Diffstat (limited to 'services/issue')
-rw-r--r--services/issue/comments.go40
-rw-r--r--services/issue/commit.go18
-rw-r--r--services/issue/issue.go13
-rw-r--r--services/issue/milestone.go4
-rw-r--r--services/issue/milestone_test.go8
-rw-r--r--services/issue/status.go46
-rw-r--r--services/issue/suggestion.go73
-rw-r--r--services/issue/suggestion_test.go57
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)
+ })
+ }
+}