aboutsummaryrefslogtreecommitdiffstats
path: root/models/issues
diff options
context:
space:
mode:
Diffstat (limited to 'models/issues')
-rw-r--r--models/issues/comment.go231
-rw-r--r--models/issues/comment_code.go5
-rw-r--r--models/issues/comment_list.go35
-rw-r--r--models/issues/dependency.go123
-rw-r--r--models/issues/issue.go18
-rw-r--r--models/issues/issue_index.go22
-rw-r--r--models/issues/issue_label.go207
-rw-r--r--models/issues/issue_list.go45
-rw-r--r--models/issues/issue_lock.go33
-rw-r--r--models/issues/issue_search.go8
-rw-r--r--models/issues/issue_stats.go5
-rw-r--r--models/issues/issue_test.go7
-rw-r--r--models/issues/issue_update.go390
-rw-r--r--models/issues/issue_xref.go2
-rw-r--r--models/issues/label.go71
-rw-r--r--models/issues/milestone.go176
-rw-r--r--models/issues/pull.go108
-rw-r--r--models/issues/pull_list.go3
-rw-r--r--models/issues/pull_test.go55
-rw-r--r--models/issues/reaction.go18
-rw-r--r--models/issues/review.go572
-rw-r--r--models/issues/review_list.go2
-rw-r--r--models/issues/stopwatch.go167
-rw-r--r--models/issues/stopwatch_test.go61
-rw-r--r--models/issues/tracked_time.go151
25 files changed, 1017 insertions, 1498 deletions
diff --git a/models/issues/comment.go b/models/issues/comment.go
index ab9b2042f3..d22f08fa87 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -9,6 +9,7 @@ import (
"context"
"fmt"
"html/template"
+ "slices"
"strconv"
"unicode/utf8"
@@ -196,12 +197,7 @@ func (t CommentType) HasMailReplySupport() bool {
}
func (t CommentType) CountedAsConversation() bool {
- for _, ct := range ConversationCountedCommentType() {
- if t == ct {
- return true
- }
- }
- return false
+ return slices.Contains(ConversationCountedCommentType(), t)
}
// ConversationCountedCommentType returns the comment types that are counted as a conversation
@@ -614,7 +610,7 @@ func UpdateCommentAttachments(ctx context.Context, c *Comment, uuids []string) e
if err != nil {
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
}
- for i := 0; i < len(attachments); i++ {
+ for i := range attachments {
attachments[i].IssueID = c.IssueID
attachments[i].CommentID = c.ID
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
@@ -719,7 +715,8 @@ func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository
return nil
}
-func (c *Comment) loadReview(ctx context.Context) (err error) {
+// LoadReview loads the associated review
+func (c *Comment) LoadReview(ctx context.Context) (err error) {
if c.ReviewID == 0 {
return nil
}
@@ -736,11 +733,6 @@ func (c *Comment) loadReview(ctx context.Context) (err error) {
return nil
}
-// LoadReview loads the associated review
-func (c *Comment) LoadReview(ctx context.Context) error {
- return c.loadReview(ctx)
-}
-
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
func (c *Comment) DiffSide() string {
if c.Line < 0 {
@@ -774,81 +766,73 @@ func (c *Comment) CodeCommentLink(ctx context.Context) string {
// CreateComment creates comment with context
func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- e := db.GetEngine(ctx)
- var LabelID int64
- if opts.Label != nil {
- LabelID = opts.Label.ID
- }
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ var LabelID int64
+ if opts.Label != nil {
+ LabelID = opts.Label.ID
+ }
- var commentMetaData *CommentMetaData
- if opts.ProjectColumnTitle != "" {
- commentMetaData = &CommentMetaData{
- ProjectColumnID: opts.ProjectColumnID,
- ProjectColumnTitle: opts.ProjectColumnTitle,
- ProjectTitle: opts.ProjectTitle,
+ var commentMetaData *CommentMetaData
+ if opts.ProjectColumnTitle != "" {
+ commentMetaData = &CommentMetaData{
+ ProjectColumnID: opts.ProjectColumnID,
+ ProjectColumnTitle: opts.ProjectColumnTitle,
+ ProjectTitle: opts.ProjectTitle,
+ }
}
- }
- comment := &Comment{
- Type: opts.Type,
- PosterID: opts.Doer.ID,
- Poster: opts.Doer,
- IssueID: opts.Issue.ID,
- LabelID: LabelID,
- OldMilestoneID: opts.OldMilestoneID,
- MilestoneID: opts.MilestoneID,
- OldProjectID: opts.OldProjectID,
- ProjectID: opts.ProjectID,
- TimeID: opts.TimeID,
- RemovedAssignee: opts.RemovedAssignee,
- AssigneeID: opts.AssigneeID,
- AssigneeTeamID: opts.AssigneeTeamID,
- CommitID: opts.CommitID,
- CommitSHA: opts.CommitSHA,
- Line: opts.LineNum,
- Content: opts.Content,
- OldTitle: opts.OldTitle,
- NewTitle: opts.NewTitle,
- OldRef: opts.OldRef,
- NewRef: opts.NewRef,
- DependentIssueID: opts.DependentIssueID,
- TreePath: opts.TreePath,
- ReviewID: opts.ReviewID,
- Patch: opts.Patch,
- RefRepoID: opts.RefRepoID,
- RefIssueID: opts.RefIssueID,
- RefCommentID: opts.RefCommentID,
- RefAction: opts.RefAction,
- RefIsPull: opts.RefIsPull,
- IsForcePush: opts.IsForcePush,
- Invalidated: opts.Invalidated,
- CommentMetaData: commentMetaData,
- }
- if _, err = e.Insert(comment); err != nil {
- return nil, err
- }
+ comment := &Comment{
+ Type: opts.Type,
+ PosterID: opts.Doer.ID,
+ Poster: opts.Doer,
+ IssueID: opts.Issue.ID,
+ LabelID: LabelID,
+ OldMilestoneID: opts.OldMilestoneID,
+ MilestoneID: opts.MilestoneID,
+ OldProjectID: opts.OldProjectID,
+ ProjectID: opts.ProjectID,
+ TimeID: opts.TimeID,
+ RemovedAssignee: opts.RemovedAssignee,
+ AssigneeID: opts.AssigneeID,
+ AssigneeTeamID: opts.AssigneeTeamID,
+ CommitID: opts.CommitID,
+ CommitSHA: opts.CommitSHA,
+ Line: opts.LineNum,
+ Content: opts.Content,
+ OldTitle: opts.OldTitle,
+ NewTitle: opts.NewTitle,
+ OldRef: opts.OldRef,
+ NewRef: opts.NewRef,
+ DependentIssueID: opts.DependentIssueID,
+ TreePath: opts.TreePath,
+ ReviewID: opts.ReviewID,
+ Patch: opts.Patch,
+ RefRepoID: opts.RefRepoID,
+ RefIssueID: opts.RefIssueID,
+ RefCommentID: opts.RefCommentID,
+ RefAction: opts.RefAction,
+ RefIsPull: opts.RefIsPull,
+ IsForcePush: opts.IsForcePush,
+ Invalidated: opts.Invalidated,
+ CommentMetaData: commentMetaData,
+ }
+ if err = db.Insert(ctx, comment); err != nil {
+ return nil, err
+ }
- if err = opts.Repo.LoadOwner(ctx); err != nil {
- return nil, err
- }
+ if err = opts.Repo.LoadOwner(ctx); err != nil {
+ return nil, err
+ }
- if err = updateCommentInfos(ctx, opts, comment); err != nil {
- return nil, err
- }
+ if err = updateCommentInfos(ctx, opts, comment); err != nil {
+ return nil, err
+ }
- if err = comment.AddCrossReferences(ctx, opts.Doer, false); err != nil {
- return nil, err
- }
- if err = committer.Commit(); err != nil {
- return nil, err
- }
- return comment, nil
+ if err = comment.AddCrossReferences(ctx, opts.Doer, false); err != nil {
+ return nil, err
+ }
+ return comment, nil
+ })
}
func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment *Comment) (err error) {
@@ -860,7 +844,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
}
if comment.ReviewID != 0 {
if comment.Review == nil {
- if err := comment.loadReview(ctx); err != nil {
+ if err := comment.LoadReview(ctx); err != nil {
return err
}
}
@@ -1100,33 +1084,21 @@ func UpdateCommentInvalidate(ctx context.Context, c *Comment) error {
// UpdateComment updates information of comment.
func UpdateComment(ctx context.Context, c *Comment, contentVersion int, doer *user_model.User) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
-
- c.ContentVersion = contentVersion + 1
-
- affected, err := sess.ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c)
- if err != nil {
- return err
- }
- if affected == 0 {
- return ErrCommentAlreadyChanged
- }
- if err := c.LoadIssue(ctx); err != nil {
- return err
- }
- if err := c.AddCrossReferences(ctx, doer, true); err != nil {
- return err
- }
- if err := committer.Commit(); err != nil {
- return fmt.Errorf("Commit: %w", err)
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ c.ContentVersion = contentVersion + 1
- return nil
+ affected, err := db.GetEngine(ctx).ID(c.ID).AllCols().Where("content_version = ?", contentVersion).Update(c)
+ if err != nil {
+ return err
+ }
+ if affected == 0 {
+ return ErrCommentAlreadyChanged
+ }
+ if err := c.LoadIssue(ctx); err != nil {
+ return err
+ }
+ return c.AddCrossReferences(ctx, doer, true)
+ })
}
// DeleteComment deletes the comment
@@ -1285,31 +1257,28 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
return comment.IssueID, true
})
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- for _, comment := range comments {
- if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil {
- return err
- }
-
- for _, reaction := range comment.Reactions {
- reaction.IssueID = comment.IssueID
- reaction.CommentID = comment.ID
- }
- if len(comment.Reactions) > 0 {
- if err := db.Insert(ctx, comment.Reactions); err != nil {
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ for _, comment := range comments {
+ if _, err := db.GetEngine(ctx).NoAutoTime().Insert(comment); err != nil {
return err
}
+
+ for _, reaction := range comment.Reactions {
+ reaction.IssueID = comment.IssueID
+ reaction.CommentID = comment.ID
+ }
+ if len(comment.Reactions) > 0 {
+ if err := db.Insert(ctx, comment.Reactions); err != nil {
+ return err
+ }
+ }
}
- }
- for _, issueID := range issueIDs {
- if err := UpdateIssueNumComments(ctx, issueID); err != nil {
- return err
+ for _, issueID := range issueIDs {
+ if err := UpdateIssueNumComments(ctx, issueID); err != nil {
+ return err
+ }
}
- }
- return committer.Commit()
+ return nil
+ })
}
diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
index b562aab500..55e67a1243 100644
--- a/models/issues/comment_code.go
+++ b/models/issues/comment_code.go
@@ -5,6 +5,7 @@ package issues
import (
"context"
+ "strconv"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/renderhelper"
@@ -114,7 +115,9 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
}
var err error
- rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo)
+ rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{
+ FootnoteContextID: strconv.FormatInt(comment.ID, 10),
+ })
if comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content); err != nil {
return nil, err
}
diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go
index c483ada75a..f6c485449f 100644
--- a/models/issues/comment_list.go
+++ b/models/issues/comment_list.go
@@ -57,10 +57,7 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
commentLabels := make(map[int64]*Label, len(labelIDs))
left := len(labelIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", labelIDs[:limit]).
Rows(new(Label))
@@ -107,10 +104,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -146,10 +140,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -184,10 +175,7 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
assignees := make(map[int64]*user_model.User, len(assigneeIDs))
left := len(assigneeIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", assigneeIDs[:limit]).
Rows(new(user_model.User))
@@ -256,10 +244,7 @@ func (comments CommentList) LoadIssues(ctx context.Context) error {
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("id", issueIDs[:limit]).
Rows(new(Issue))
@@ -313,10 +298,7 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := e.
In("id", issueIDs[:limit]).
Rows(new(Issue))
@@ -392,10 +374,7 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) {
commentsIDs := comments.getAttachmentCommentIDs()
left := len(commentsIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("comment_id", commentsIDs[:limit]).
Rows(new(repo_model.Attachment))
diff --git a/models/issues/dependency.go b/models/issues/dependency.go
index 146dd1887d..0eaa47e359 100644
--- a/models/issues/dependency.go
+++ b/models/issues/dependency.go
@@ -128,79 +128,64 @@ const (
// CreateIssueDependency creates a new dependency for an issue
func CreateIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- // Check if it already exists
- exists, err := issueDepExists(ctx, issue.ID, dep.ID)
- if err != nil {
- return err
- }
- if exists {
- return ErrDependencyExists{issue.ID, dep.ID}
- }
- // And if it would be circular
- circular, err := issueDepExists(ctx, dep.ID, issue.ID)
- if err != nil {
- return err
- }
- if circular {
- return ErrCircularDependency{issue.ID, dep.ID}
- }
-
- if err := db.Insert(ctx, &IssueDependency{
- UserID: user.ID,
- IssueID: issue.ID,
- DependencyID: dep.ID,
- }); err != nil {
- return err
- }
-
- // Add comment referencing the new dependency
- if err = createIssueDependencyComment(ctx, user, issue, dep, true); err != nil {
- return err
- }
-
- return committer.Commit()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ // Check if it already exists
+ exists, err := issueDepExists(ctx, issue.ID, dep.ID)
+ if err != nil {
+ return err
+ }
+ if exists {
+ return ErrDependencyExists{issue.ID, dep.ID}
+ }
+ // And if it would be circular
+ circular, err := issueDepExists(ctx, dep.ID, issue.ID)
+ if err != nil {
+ return err
+ }
+ if circular {
+ return ErrCircularDependency{issue.ID, dep.ID}
+ }
+
+ if err := db.Insert(ctx, &IssueDependency{
+ UserID: user.ID,
+ IssueID: issue.ID,
+ DependencyID: dep.ID,
+ }); err != nil {
+ return err
+ }
+
+ // Add comment referencing the new dependency
+ return createIssueDependencyComment(ctx, user, issue, dep, true)
+ })
}
// RemoveIssueDependency removes a dependency from an issue
func RemoveIssueDependency(ctx context.Context, user *user_model.User, issue, dep *Issue, depType DependencyType) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- var issueDepToDelete IssueDependency
-
- switch depType {
- case DependencyTypeBlockedBy:
- issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
- case DependencyTypeBlocking:
- issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
- default:
- return ErrUnknownDependencyType{depType}
- }
-
- affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete)
- if err != nil {
- return err
- }
-
- // If we deleted nothing, the dependency did not exist
- if affected <= 0 {
- return ErrDependencyNotExists{issue.ID, dep.ID}
- }
-
- // Add comment referencing the removed dependency
- if err = createIssueDependencyComment(ctx, user, issue, dep, false); err != nil {
- return err
- }
- return committer.Commit()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ var issueDepToDelete IssueDependency
+
+ switch depType {
+ case DependencyTypeBlockedBy:
+ issueDepToDelete = IssueDependency{IssueID: issue.ID, DependencyID: dep.ID}
+ case DependencyTypeBlocking:
+ issueDepToDelete = IssueDependency{IssueID: dep.ID, DependencyID: issue.ID}
+ default:
+ return ErrUnknownDependencyType{depType}
+ }
+
+ affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete)
+ if err != nil {
+ return err
+ }
+
+ // If we deleted nothing, the dependency did not exist
+ if affected <= 0 {
+ return ErrDependencyNotExists{issue.ID, dep.ID}
+ }
+
+ // Add comment referencing the removed dependency
+ return createIssueDependencyComment(ctx, user, issue, dep, false)
+ })
}
// Check if the dependency already exists
diff --git a/models/issues/issue.go b/models/issues/issue.go
index a86d50ca9d..ef651359ab 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -755,18 +755,14 @@ func (issue *Issue) HasOriginalAuthor() bool {
// InsertIssues insert issues to database
func InsertIssues(ctx context.Context, issues ...*Issue) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- for _, issue := range issues {
- if err := insertIssue(ctx, issue); err != nil {
- return err
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ for _, issue := range issues {
+ if err := insertIssue(ctx, issue); err != nil {
+ return err
+ }
}
- }
- return committer.Commit()
+ return nil
+ })
}
func insertIssue(ctx context.Context, issue *Issue) error {
diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go
index 2eb61858bf..1fe4a08a09 100644
--- a/models/issues/issue_index.go
+++ b/models/issues/issue_index.go
@@ -12,20 +12,12 @@ import (
// RecalculateIssueIndexForRepo create issue_index for repo if not exist and
// update it based on highest index of existing issues assigned to a repo
func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ var maxIndex int64
+ if _, err := db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil {
+ return err
+ }
- var maxIndex int64
- if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil {
- return err
- }
-
- if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex); err != nil {
- return err
- }
-
- return committer.Commit()
+ return db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex)
+ })
}
diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go
index 10fc821454..151469a9b8 100644
--- a/models/issues/issue_label.go
+++ b/models/issues/issue_label.go
@@ -88,36 +88,28 @@ func NewIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m
return nil
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err = issue.LoadRepo(ctx); err != nil {
- return err
- }
-
- // Do NOT add invalid labels
- if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID {
- return nil
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err = issue.LoadRepo(ctx); err != nil {
+ return err
+ }
- if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil {
- return nil
- }
+ // Do NOT add invalid labels
+ if issue.RepoID != label.RepoID && issue.Repo.OwnerID != label.OrgID {
+ return nil
+ }
- if err = newIssueLabel(ctx, issue, label, doer); err != nil {
- return err
- }
+ if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, label, doer); err != nil {
+ return nil
+ }
- issue.isLabelsLoaded = false
- issue.Labels = nil
- if err = issue.LoadLabels(ctx); err != nil {
- return err
- }
+ if err = newIssueLabel(ctx, issue, label, doer); err != nil {
+ return err
+ }
- return committer.Commit()
+ issue.isLabelsLoaded = false
+ issue.Labels = nil
+ return issue.LoadLabels(ctx)
+ })
}
// newIssueLabels add labels to an issue. It will check if the labels are valid for the issue
@@ -151,24 +143,16 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
// NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err = newIssueLabels(ctx, issue, labels, doer); err != nil {
- return err
- }
-
- // reload all labels
- issue.isLabelsLoaded = false
- issue.Labels = nil
- if err = issue.LoadLabels(ctx); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err = newIssueLabels(ctx, issue, labels, doer); err != nil {
+ return err
+ }
- return committer.Commit()
+ // reload all labels
+ issue.isLabelsLoaded = false
+ issue.Labels = nil
+ return issue.LoadLabels(ctx)
+ })
}
func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_model.User) (err error) {
@@ -206,6 +190,7 @@ func DeleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *use
}
issue.Labels = nil
+ issue.isLabelsLoaded = false
return issue.LoadLabels(ctx)
}
@@ -364,35 +349,23 @@ func clearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User)
// ClearIssueLabels removes all issue labels as the given user.
// Triggers appropriate WebHooks, if any.
func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- } else if err = issue.LoadPullRequest(ctx); err != nil {
- return err
- }
-
- perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
- if err != nil {
- return err
- }
- if !perm.CanWriteIssuesOrPulls(issue.IsPull) {
- return ErrRepoLabelNotExist{}
- }
-
- if err = clearIssueLabels(ctx, issue, doer); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err := issue.LoadRepo(ctx); err != nil {
+ return err
+ } else if err = issue.LoadPullRequest(ctx); err != nil {
+ return err
+ }
- if err = committer.Commit(); err != nil {
- return fmt.Errorf("Commit: %w", err)
- }
+ perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
+ if err != nil {
+ return err
+ }
+ if !perm.CanWriteIssuesOrPulls(issue.IsPull) {
+ return ErrRepoLabelNotExist{}
+ }
- return nil
+ return clearIssueLabels(ctx, issue, doer)
+ })
}
type labelSorter []*Label
@@ -437,69 +410,61 @@ func RemoveDuplicateExclusiveLabels(labels []*Label) []*Label {
// ReplaceIssueLabels removes all current labels and add new labels to the issue.
// Triggers appropriate WebHooks, if any.
func ReplaceIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *user_model.User) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err = issue.LoadRepo(ctx); err != nil {
+ return err
+ }
- if err = issue.LoadRepo(ctx); err != nil {
- return err
- }
+ if err = issue.LoadLabels(ctx); err != nil {
+ return err
+ }
- if err = issue.LoadLabels(ctx); err != nil {
- return err
- }
+ labels = RemoveDuplicateExclusiveLabels(labels)
- labels = RemoveDuplicateExclusiveLabels(labels)
+ sort.Sort(labelSorter(labels))
+ sort.Sort(labelSorter(issue.Labels))
- sort.Sort(labelSorter(labels))
- sort.Sort(labelSorter(issue.Labels))
+ var toAdd, toRemove []*Label
- var toAdd, toRemove []*Label
+ addIndex, removeIndex := 0, 0
+ for addIndex < len(labels) && removeIndex < len(issue.Labels) {
+ addLabel := labels[addIndex]
+ removeLabel := issue.Labels[removeIndex]
+ if addLabel.ID == removeLabel.ID {
+ // Silently drop invalid labels
+ if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID {
+ toRemove = append(toRemove, removeLabel)
+ }
- addIndex, removeIndex := 0, 0
- for addIndex < len(labels) && removeIndex < len(issue.Labels) {
- addLabel := labels[addIndex]
- removeLabel := issue.Labels[removeIndex]
- if addLabel.ID == removeLabel.ID {
- // Silently drop invalid labels
- if removeLabel.RepoID != issue.RepoID && removeLabel.OrgID != issue.Repo.OwnerID {
+ addIndex++
+ removeIndex++
+ } else if addLabel.ID < removeLabel.ID {
+ // Only add if the label is valid
+ if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID {
+ toAdd = append(toAdd, addLabel)
+ }
+ addIndex++
+ } else {
toRemove = append(toRemove, removeLabel)
+ removeIndex++
}
-
- addIndex++
- removeIndex++
- } else if addLabel.ID < removeLabel.ID {
- // Only add if the label is valid
- if addLabel.RepoID == issue.RepoID || addLabel.OrgID == issue.Repo.OwnerID {
- toAdd = append(toAdd, addLabel)
- }
- addIndex++
- } else {
- toRemove = append(toRemove, removeLabel)
- removeIndex++
}
- }
- toAdd = append(toAdd, labels[addIndex:]...)
- toRemove = append(toRemove, issue.Labels[removeIndex:]...)
+ toAdd = append(toAdd, labels[addIndex:]...)
+ toRemove = append(toRemove, issue.Labels[removeIndex:]...)
- if len(toAdd) > 0 {
- if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
- return fmt.Errorf("addLabels: %w", err)
+ if len(toAdd) > 0 {
+ if err = newIssueLabels(ctx, issue, toAdd, doer); err != nil {
+ return fmt.Errorf("addLabels: %w", err)
+ }
}
- }
- for _, l := range toRemove {
- if err = deleteIssueLabel(ctx, issue, l, doer); err != nil {
- return fmt.Errorf("removeLabel: %w", err)
+ for _, l := range toRemove {
+ if err = deleteIssueLabel(ctx, issue, l, doer); err != nil {
+ return fmt.Errorf("removeLabel: %w", err)
+ }
}
- }
-
- issue.Labels = nil
- if err = issue.LoadLabels(ctx); err != nil {
- return err
- }
- return committer.Commit()
+ issue.Labels = nil
+ return issue.LoadLabels(ctx)
+ })
}
diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go
index 6c74b533b3..26b93189b8 100644
--- a/models/issues/issue_list.go
+++ b/models/issues/issue_list.go
@@ -42,10 +42,7 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi
repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs))
left := len(repoIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", repoIDs[:limit]).
Find(&repoMaps)
@@ -116,10 +113,7 @@ func (issues IssueList) LoadLabels(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", issueIDs[:limit]).
@@ -171,10 +165,7 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error {
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
left := len(milestoneIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
err := db.GetEngine(ctx).
In("id", milestoneIDs[:limit]).
Find(&milestoneMaps)
@@ -203,10 +194,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error {
}
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
projects := make([]*projectWithIssueID, 0, limit)
err := db.GetEngine(ctx).
@@ -245,10 +233,7 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error {
issueIDs := issues.getIssueIDs()
left := len(issueIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("issue_assignees").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()).
@@ -306,10 +291,7 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error {
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(PullRequest))
@@ -354,10 +336,7 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) {
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).
In("issue_id", issuesIDs[:limit]).
Rows(new(repo_model.Attachment))
@@ -399,10 +378,7 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er
issuesIDs := issues.getIssueIDs()
left := len(issuesIDs)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
rows, err := db.GetEngine(ctx).Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", issuesIDs[:limit]).
@@ -466,10 +442,7 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) {
left := len(ids)
for left > 0 {
- limit := db.DefaultMaxInSize
- if left < limit {
- limit = left
- }
+ limit := min(left, db.DefaultMaxInSize)
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
rows, err := db.GetEngine(ctx).Table("tracked_time").
diff --git a/models/issues/issue_lock.go b/models/issues/issue_lock.go
index fa0d128f74..2e5bf64cc6 100644
--- a/models/issues/issue_lock.go
+++ b/models/issues/issue_lock.go
@@ -47,26 +47,19 @@ func updateIssueLock(ctx context.Context, opts *IssueLockOptions, lock bool) err
commentType = CommentTypeUnlock
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err := UpdateIssueCols(ctx, opts.Issue, "is_locked"); err != nil {
+ return err
+ }
- opt := &CreateCommentOptions{
- Doer: opts.Doer,
- Issue: opts.Issue,
- Repo: opts.Issue.Repo,
- Type: commentType,
- Content: opts.Reason,
- }
- if _, err := CreateComment(ctx, opt); err != nil {
+ opt := &CreateCommentOptions{
+ Doer: opts.Doer,
+ Issue: opts.Issue,
+ Repo: opts.Issue.Repo,
+ Type: commentType,
+ Content: opts.Reason,
+ }
+ _, err := CreateComment(ctx, opt)
return err
- }
-
- return committer.Commit()
+ })
}
diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go
index f9e1fbeb14..79bd6a19b0 100644
--- a/models/issues/issue_search.go
+++ b/models/issues/issue_search.go
@@ -24,7 +24,7 @@ import (
const ScopeSortPrefix = "scope-"
// IssuesOptions represents options of an issue.
-type IssuesOptions struct { //nolint
+type IssuesOptions struct { //nolint:revive // export stutter
Paginator *db.ListOptions
RepoIDs []int64 // overwrites RepoCond if the length is not 0
AllPublic bool // include also all public repositories
@@ -73,8 +73,8 @@ func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOption
// sortType string
func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
// Since this sortType is dynamically created, it has to be treated specially.
- if strings.HasPrefix(sortType, ScopeSortPrefix) {
- scope := strings.TrimPrefix(sortType, ScopeSortPrefix)
+ if after, ok := strings.CutPrefix(sortType, ScopeSortPrefix); ok {
+ scope := after
sess.Join("LEFT", "issue_label", "issue.id = issue_label.issue_id")
// "exclusive_order=0" means "no order is set", so exclude it from the JOIN criteria and then "LEFT JOIN" result is also null
sess.Join("LEFT", "label", "label.id = issue_label.label_id AND label.exclusive_order <> 0 AND label.name LIKE ?", scope+"/%")
@@ -88,6 +88,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
sess.Asc("issue.created_unix").Asc("issue.id")
case "recentupdate":
sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id")
+ case "recentclose":
+ sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id")
case "leastupdate":
sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id")
case "mostcomment":
diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go
index 50409fbbd8..adedaa3d3a 100644
--- a/models/issues/issue_stats.go
+++ b/models/issues/issue_stats.go
@@ -94,10 +94,7 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error
// ids in a temporary table and join from them.
accum := &IssueStats{}
for i := 0; i < len(opts.IssueIDs); {
- chunk := i + MaxQueryParameters
- if chunk > len(opts.IssueIDs) {
- chunk = len(opts.IssueIDs)
- }
+ chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk])
if err != nil {
return nil, err
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index 18571e3aaa..1c5db55bbc 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -5,6 +5,7 @@ package issues_test
import (
"fmt"
+ "slices"
"sort"
"sync"
"testing"
@@ -270,7 +271,7 @@ func TestIssue_ResolveMentions(t *testing.T) {
for i, user := range resolved {
ids[i] = user.ID
}
- sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
+ slices.Sort(ids)
assert.Equal(t, expected, ids)
}
@@ -292,7 +293,7 @@ func TestResourceIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
var wg sync.WaitGroup
- for i := 0; i < 100; i++ {
+ for i := range 100 {
wg.Add(1)
go func(i int) {
testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0)
@@ -314,7 +315,7 @@ func TestCorrectIssueStats(t *testing.T) {
issueAmount := issues_model.MaxQueryParameters + 10
var wg sync.WaitGroup
- for i := 0; i < issueAmount; i++ {
+ for i := range issueAmount {
wg.Add(1)
go func(i int) {
testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0)
diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go
index 7ddf7ee901..1c16817491 100644
--- a/models/issues/issue_update.go
+++ b/models/issues/issue_update.go
@@ -12,9 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
- project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
- system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@@ -169,20 +167,9 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm
return nil, err
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- comment, err := SetIssueAsClosed(ctx, issue, doer, false)
- if err != nil {
- return nil, err
- }
- if err := committer.Commit(); err != nil {
- return nil, err
- }
- return comment, nil
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ return SetIssueAsClosed(ctx, issue, doer, false)
+ })
}
// ReopenIssue changes issue status to open.
@@ -194,88 +181,64 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com
return nil, err
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- comment, err := setIssueAsReopen(ctx, issue, doer)
- if err != nil {
- return nil, err
- }
- if err := committer.Commit(); err != nil {
- return nil, err
- }
- return comment, nil
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ return setIssueAsReopen(ctx, issue, doer)
+ })
}
// ChangeIssueTitle changes the title of this issue, as the given user.
func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, oldTitle string) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- issue.Title = util.EllipsisDisplayString(issue.Title, 255)
- if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
- return fmt.Errorf("updateIssueCols: %w", err)
- }
-
- if err = issue.LoadRepo(ctx); err != nil {
- return fmt.Errorf("loadRepo: %w", err)
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ issue.Title = util.EllipsisDisplayString(issue.Title, 255)
+ if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
+ return fmt.Errorf("updateIssueCols: %w", err)
+ }
- opts := &CreateCommentOptions{
- Type: CommentTypeChangeTitle,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- OldTitle: oldTitle,
- NewTitle: issue.Title,
- }
- if _, err = CreateComment(ctx, opts); err != nil {
- return fmt.Errorf("createComment: %w", err)
- }
- if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
- return err
- }
+ if err = issue.LoadRepo(ctx); err != nil {
+ return fmt.Errorf("loadRepo: %w", err)
+ }
- return committer.Commit()
+ opts := &CreateCommentOptions{
+ Type: CommentTypeChangeTitle,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ OldTitle: oldTitle,
+ NewTitle: issue.Title,
+ }
+ if _, err = CreateComment(ctx, opts); err != nil {
+ return fmt.Errorf("createComment: %w", err)
+ }
+ return issue.AddCrossReferences(ctx, doer, true)
+ })
}
// ChangeIssueRef changes the branch of this issue, as the given user.
func ChangeIssueRef(ctx context.Context, issue *Issue, doer *user_model.User, oldRef string) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err = UpdateIssueCols(ctx, issue, "ref"); err != nil {
- return fmt.Errorf("updateIssueCols: %w", err)
- }
-
- if err = issue.LoadRepo(ctx); err != nil {
- return fmt.Errorf("loadRepo: %w", err)
- }
- oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix)
- newRefFriendly := strings.TrimPrefix(issue.Ref, git.BranchPrefix)
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err = UpdateIssueCols(ctx, issue, "ref"); err != nil {
+ return fmt.Errorf("updateIssueCols: %w", err)
+ }
- opts := &CreateCommentOptions{
- Type: CommentTypeChangeIssueRef,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- OldRef: oldRefFriendly,
- NewRef: newRefFriendly,
- }
- if _, err = CreateComment(ctx, opts); err != nil {
- return fmt.Errorf("createComment: %w", err)
- }
+ if err = issue.LoadRepo(ctx); err != nil {
+ return fmt.Errorf("loadRepo: %w", err)
+ }
+ oldRefFriendly := strings.TrimPrefix(oldRef, git.BranchPrefix)
+ newRefFriendly := strings.TrimPrefix(issue.Ref, git.BranchPrefix)
- return committer.Commit()
+ opts := &CreateCommentOptions{
+ Type: CommentTypeChangeIssueRef,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ OldRef: oldRefFriendly,
+ NewRef: newRefFriendly,
+ }
+ if _, err = CreateComment(ctx, opts); err != nil {
+ return fmt.Errorf("createComment: %w", err)
+ }
+ return nil
+ })
}
// AddDeletePRBranchComment adds delete branch comment for pull request issue
@@ -297,64 +260,56 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo *
// UpdateIssueAttachments update attachments by UUIDs for the issue
func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
- if err != nil {
- return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
- }
- for i := 0; i < len(attachments); i++ {
- attachments[i].IssueID = issueID
- if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
- return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
+ if err != nil {
+ return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
}
- }
- return committer.Commit()
+ for i := range attachments {
+ attachments[i].IssueID = issueID
+ if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
+ return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
+ }
+ }
+ return nil
+ })
}
// ChangeIssueContent changes issue content, as the given user.
func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string, contentVersion int) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0)
- if err != nil {
- return fmt.Errorf("HasIssueContentHistory: %w", err)
- }
- if !hasContentHistory {
- if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0,
- issue.CreatedUnix, issue.Content, true); err != nil {
- return fmt.Errorf("SaveIssueContentHistory: %w", err)
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ hasContentHistory, err := HasIssueContentHistory(ctx, issue.ID, 0)
+ if err != nil {
+ return fmt.Errorf("HasIssueContentHistory: %w", err)
+ }
+ if !hasContentHistory {
+ if err = SaveIssueContentHistory(ctx, issue.PosterID, issue.ID, 0,
+ issue.CreatedUnix, issue.Content, true); err != nil {
+ return fmt.Errorf("SaveIssueContentHistory: %w", err)
+ }
}
- }
- issue.Content = content
- issue.ContentVersion = contentVersion + 1
+ issue.Content = content
+ issue.ContentVersion = contentVersion + 1
- affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue)
- if err != nil {
- return err
- }
- if affected == 0 {
- return ErrIssueAlreadyChanged
- }
-
- if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
- timeutil.TimeStampNow(), issue.Content, false); err != nil {
- return fmt.Errorf("SaveIssueContentHistory: %w", err)
- }
+ affected, err := db.GetEngine(ctx).ID(issue.ID).Cols("content", "content_version").Where("content_version = ?", contentVersion).Update(issue)
+ if err != nil {
+ return err
+ }
+ if affected == 0 {
+ return ErrIssueAlreadyChanged
+ }
- if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
- return fmt.Errorf("addCrossReferences: %w", err)
- }
+ if err = SaveIssueContentHistory(ctx, doer.ID, issue.ID, 0,
+ timeutil.TimeStampNow(), issue.Content, false); err != nil {
+ return fmt.Errorf("SaveIssueContentHistory: %w", err)
+ }
- return committer.Commit()
+ if err = issue.AddCrossReferences(ctx, doer, true); err != nil {
+ return fmt.Errorf("addCrossReferences: %w", err)
+ }
+ return nil
+ })
}
// NewIssueOptions represents the options of a new issue.
@@ -514,23 +469,19 @@ func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeuti
if issue.DeadlineUnix == deadlineUnix {
return nil
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- // Update the deadline
- if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
- return err
- }
-
- // Make the comment
- if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil {
- return fmt.Errorf("createRemovedDueDateComment: %w", err)
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ // Update the deadline
+ if err = UpdateIssueCols(ctx, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
+ return err
+ }
- return committer.Commit()
+ // Make the comment
+ if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil {
+ return fmt.Errorf("createRemovedDueDateComment: %w", err)
+ }
+ return nil
+ })
}
// FindAndUpdateIssueMentions finds users mentioned in the given content string, and saves them in the database.
@@ -715,138 +666,13 @@ func UpdateReactionsMigrationsByType(ctx context.Context, gitServiceType api.Git
return err
}
-// DeleteIssuesByRepoID deletes issues by repositories id
-func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) {
- // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
- // so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
- sess := db.GetEngine(ctx)
-
- for {
- issueIDs := make([]int64, 0, db.DefaultMaxInSize)
-
- err := sess.Table(&Issue{}).Where("repo_id = ?", repoID).OrderBy("id").Limit(db.DefaultMaxInSize).Cols("id").Find(&issueIDs)
- if err != nil {
- return nil, err
- }
-
- if len(issueIDs) == 0 {
- break
- }
-
- // Delete content histories
- _, err = sess.In("issue_id", issueIDs).Delete(&ContentHistory{})
- if err != nil {
- return nil, err
- }
-
- // Delete comments and attachments
- _, err = sess.In("issue_id", issueIDs).Delete(&Comment{})
- if err != nil {
- return nil, err
- }
-
- // Dependencies for issues in this repository
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueDependency{})
- if err != nil {
- return nil, err
- }
-
- // Delete dependencies for issues in other repositories
- _, err = sess.In("dependency_id", issueIDs).Delete(&IssueDependency{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&TrackedTime{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&project_model.ProjectIssue{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("dependent_issue_id", issueIDs).Delete(&Comment{})
- if err != nil {
- return nil, err
- }
-
- var attachments []*repo_model.Attachment
- err = sess.In("issue_id", issueIDs).Find(&attachments)
- if err != nil {
- return nil, err
- }
-
- for j := range attachments {
- attachmentPaths = append(attachmentPaths, attachments[j].RelativePath())
- }
-
- _, err = sess.In("issue_id", issueIDs).Delete(&repo_model.Attachment{})
- if err != nil {
- return nil, err
- }
-
- _, err = sess.In("id", issueIDs).Delete(&Issue{})
- if err != nil {
- return nil, err
- }
- }
-
- return attachmentPaths, err
-}
-
-// DeleteOrphanedIssues delete issues without a repo
-func DeleteOrphanedIssues(ctx context.Context) error {
- var attachmentPaths []string
- err := db.WithTx(ctx, func(ctx context.Context) error {
- var ids []int64
-
- if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
- Join("LEFT", "repository", "issue.repo_id=repository.id").
- Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
- Find(&ids); err != nil {
- return err
- }
-
- for i := range ids {
- paths, err := DeleteIssuesByRepoID(ctx, ids[i])
- if err != nil {
- return err
- }
- attachmentPaths = append(attachmentPaths, paths...)
- }
-
- return nil
- })
- if err != nil {
- return err
- }
-
- // Remove issue attachment files.
- for i := range attachmentPaths {
- // FIXME: it's not right, because the attachment might not be on local filesystem
- system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i])
+func GetOrphanedIssueRepoIDs(ctx context.Context) ([]int64, error) {
+ var repoIDs []int64
+ if err := db.GetEngine(ctx).Table("issue").Distinct("issue.repo_id").
+ Join("LEFT", "repository", "issue.repo_id=repository.id").
+ Where(builder.IsNull{"repository.id"}).
+ Find(&repoIDs); err != nil {
+ return nil, err
}
- return nil
+ return repoIDs, nil
}
diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go
index e2e35859df..f8495929cf 100644
--- a/models/issues/issue_xref.go
+++ b/models/issues/issue_xref.go
@@ -235,7 +235,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
// AddCrossReferences add cross references
func (c *Comment) AddCrossReferences(stdCtx context.Context, doer *user_model.User, removeOld bool) error {
- if c.Type != CommentTypeCode && c.Type != CommentTypeComment {
+ if !c.Type.HasContentSupport() {
return nil
}
if err := c.LoadIssue(stdCtx); err != nil {
diff --git a/models/issues/label.go b/models/issues/label.go
index cfbe100926..25d6f1303e 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -209,24 +209,20 @@ func NewLabel(ctx context.Context, l *Label) error {
// NewLabels creates new labels
func NewLabels(ctx context.Context, labels ...*Label) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- for _, l := range labels {
- color, err := label.NormalizeColor(l.Color)
- if err != nil {
- return err
- }
- l.Color = color
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ for _, l := range labels {
+ color, err := label.NormalizeColor(l.Color)
+ if err != nil {
+ return err
+ }
+ l.Color = color
- if err := db.Insert(ctx, l); err != nil {
- return err
+ if err := db.Insert(ctx, l); err != nil {
+ return err
+ }
}
- }
- return committer.Commit()
+ return nil
+ })
}
// UpdateLabel updates label information.
@@ -250,35 +246,26 @@ func DeleteLabel(ctx context.Context, id, labelID int64) error {
return err
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- sess := db.GetEngine(ctx)
-
- if l.BelongsToOrg() && l.OrgID != id {
- return nil
- }
- if l.BelongsToRepo() && l.RepoID != id {
- return nil
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if l.BelongsToOrg() && l.OrgID != id {
+ return nil
+ }
+ if l.BelongsToRepo() && l.RepoID != id {
+ return nil
+ }
- if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {
- return err
- } else if _, err = sess.
- Where("label_id = ?", labelID).
- Delete(new(IssueLabel)); err != nil {
- return err
- }
+ if _, err = db.DeleteByID[Label](ctx, labelID); err != nil {
+ return err
+ } else if _, err = db.GetEngine(ctx).
+ Where("label_id = ?", labelID).
+ Delete(new(IssueLabel)); err != nil {
+ return err
+ }
- // delete comments about now deleted label_id
- if _, err = sess.Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{}); err != nil {
+ // delete comments about now deleted label_id
+ _, err = db.GetEngine(ctx).Where("label_id = ?", labelID).Cols("label_id").Delete(&Comment{})
return err
- }
-
- return committer.Commit()
+ })
}
// GetLabelByID returns a label by given ID.
diff --git a/models/issues/milestone.go b/models/issues/milestone.go
index 4c9bae58f7..373f39f4ff 100644
--- a/models/issues/milestone.go
+++ b/models/issues/milestone.go
@@ -105,22 +105,16 @@ func (m *Milestone) State() api.StateType {
// NewMilestone creates new milestone of repository.
func NewMilestone(ctx context.Context, m *Milestone) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ m.Name = strings.TrimSpace(m.Name)
- m.Name = strings.TrimSpace(m.Name)
-
- if err = db.Insert(ctx, m); err != nil {
- return err
- }
+ if err = db.Insert(ctx, m); err != nil {
+ return err
+ }
- if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil {
+ _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID)
return err
- }
- return committer.Commit()
+ })
}
// HasMilestoneByRepoID returns if the milestone exists in the repository.
@@ -155,28 +149,23 @@ func GetMilestoneByRepoIDANDName(ctx context.Context, repoID int64, name string)
// UpdateMilestone updates information of given milestone.
func UpdateMilestone(ctx context.Context, m *Milestone, oldIsClosed bool) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if m.IsClosed && !oldIsClosed {
- m.ClosedDateUnix = timeutil.TimeStampNow()
- }
-
- if err := updateMilestone(ctx, m); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if m.IsClosed && !oldIsClosed {
+ m.ClosedDateUnix = timeutil.TimeStampNow()
+ }
- // if IsClosed changed, update milestone numbers of repository
- if oldIsClosed != m.IsClosed {
- if err := updateRepoMilestoneNum(ctx, m.RepoID); err != nil {
+ if err := updateMilestone(ctx, m); err != nil {
return err
}
- }
- return committer.Commit()
+ // if IsClosed changed, update milestone numbers of repository
+ if oldIsClosed != m.IsClosed {
+ if err := updateRepoMilestoneNum(ctx, m.RepoID); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
}
func updateMilestone(ctx context.Context, m *Milestone) error {
@@ -213,44 +202,28 @@ func UpdateMilestoneCounters(ctx context.Context, id int64) error {
// ChangeMilestoneStatusByRepoIDAndID changes a milestone open/closed status if the milestone ID is in the repo.
func ChangeMilestoneStatusByRepoIDAndID(ctx context.Context, repoID, milestoneID int64, isClosed bool) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- m := &Milestone{
- ID: milestoneID,
- RepoID: repoID,
- }
-
- has, err := db.GetEngine(ctx).ID(milestoneID).Where("repo_id = ?", repoID).Get(m)
- if err != nil {
- return err
- } else if !has {
- return ErrMilestoneNotExist{ID: milestoneID, RepoID: repoID}
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ m := &Milestone{
+ ID: milestoneID,
+ RepoID: repoID,
+ }
- if err := changeMilestoneStatus(ctx, m, isClosed); err != nil {
- return err
- }
+ has, err := db.GetEngine(ctx).ID(milestoneID).Where("repo_id = ?", repoID).Get(m)
+ if err != nil {
+ return err
+ } else if !has {
+ return ErrMilestoneNotExist{ID: milestoneID, RepoID: repoID}
+ }
- return committer.Commit()
+ return changeMilestoneStatus(ctx, m, isClosed)
+ })
}
// ChangeMilestoneStatus changes the milestone open/closed status.
func ChangeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := changeMilestoneStatus(ctx, m, isClosed); err != nil {
- return err
- }
-
- return committer.Commit()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ return changeMilestoneStatus(ctx, m, isClosed)
+ })
}
func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) error {
@@ -284,40 +257,34 @@ func DeleteMilestoneByRepoID(ctx context.Context, repoID, id int64) error {
return err
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if _, err = db.DeleteByID[Milestone](ctx, m.ID); err != nil {
+ return err
+ }
- numMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
- RepoID: repo.ID,
- })
- if err != nil {
- return err
- }
- numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
- RepoID: repo.ID,
- IsClosed: optional.Some(true),
- })
- if err != nil {
- return err
- }
- repo.NumMilestones = int(numMilestones)
- repo.NumClosedMilestones = int(numClosedMilestones)
+ numMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
+ RepoID: repo.ID,
+ })
+ if err != nil {
+ return err
+ }
+ numClosedMilestones, err := db.Count[Milestone](ctx, FindMilestoneOptions{
+ RepoID: repo.ID,
+ IsClosed: optional.Some(true),
+ })
+ if err != nil {
+ return err
+ }
+ repo.NumMilestones = int(numMilestones)
+ repo.NumClosedMilestones = int(numClosedMilestones)
- if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
- return err
- }
+ if _, err = db.GetEngine(ctx).ID(repo.ID).Cols("num_milestones, num_closed_milestones").Update(repo); err != nil {
+ return err
+ }
- if _, err = db.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil {
+ _, err = db.Exec(ctx, "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID)
return err
- }
- return committer.Commit()
+ })
}
func updateRepoMilestoneNum(ctx context.Context, repoID int64) error {
@@ -360,22 +327,15 @@ func InsertMilestones(ctx context.Context, ms ...*Milestone) (err error) {
return nil
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
-
- // to return the id, so we should not use batch insert
- for _, m := range ms {
- if _, err = sess.NoAutoTime().Insert(m); err != nil {
- return err
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ // to return the id, so we should not use batch insert
+ for _, m := range ms {
+ if _, err = db.GetEngine(ctx).NoAutoTime().Insert(m); err != nil {
+ return err
+ }
}
- }
- if _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID); err != nil {
+ _, err = db.Exec(ctx, "UPDATE `repository` SET num_milestones = num_milestones + ? WHERE id = ?", len(ms), ms[0].RepoID)
return err
- }
- return committer.Commit()
+ })
}
diff --git a/models/issues/pull.go b/models/issues/pull.go
index e65b214dab..00d7bfe1ca 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -364,17 +364,10 @@ func (pr *PullRequest) GetApprovers(ctx context.Context) string {
func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer) error {
maxReviewers := setting.Repository.PullRequest.DefaultMergeMessageMaxApprovers
-
if maxReviewers == 0 {
return nil
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
// Note: This doesn't page as we only expect a very limited number of reviews
reviews, err := FindLatestReviews(ctx, FindReviewOptions{
Types: []ReviewType{ReviewTypeApprove},
@@ -410,11 +403,11 @@ func (pr *PullRequest) getReviewedByLines(ctx context.Context, writer io.Writer)
}
reviewersWritten++
}
- return committer.Commit()
+ return nil
}
-// GetGitRefName returns git ref for hidden pull request branch
-func (pr *PullRequest) GetGitRefName() string {
+// GetGitHeadRefName returns git ref for hidden pull request branch
+func (pr *PullRequest) GetGitHeadRefName() string {
return fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index)
}
@@ -464,45 +457,36 @@ func (pr *PullRequest) IsFromFork() bool {
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string, pr *PullRequest) (err error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
- if err != nil {
- return fmt.Errorf("generate pull request index failed: %w", err)
- }
-
- issue.Index = idx
- issue.Title = util.EllipsisDisplayString(issue.Title, 255)
-
- if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
- Repo: repo,
- Issue: issue,
- LabelIDs: labelIDs,
- Attachments: uuids,
- IsPull: true,
- }); err != nil {
- if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
- return err
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ idx, err := db.GetNextResourceIndex(ctx, "issue_index", repo.ID)
+ if err != nil {
+ return fmt.Errorf("generate pull request index failed: %w", err)
}
- return fmt.Errorf("newIssue: %w", err)
- }
-
- pr.Index = issue.Index
- pr.BaseRepo = repo
- pr.IssueID = issue.ID
- if err = db.Insert(ctx, pr); err != nil {
- return fmt.Errorf("insert pull repo: %w", err)
- }
- if err = committer.Commit(); err != nil {
- return fmt.Errorf("Commit: %w", err)
- }
+ issue.Index = idx
+ issue.Title = util.EllipsisDisplayString(issue.Title, 255)
+
+ if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
+ Repo: repo,
+ Issue: issue,
+ LabelIDs: labelIDs,
+ Attachments: uuids,
+ IsPull: true,
+ }); err != nil {
+ if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
+ return err
+ }
+ return fmt.Errorf("newIssue: %w", err)
+ }
- return nil
+ pr.Index = issue.Index
+ pr.BaseRepo = repo
+ pr.IssueID = issue.ID
+ if err = db.Insert(ctx, pr); err != nil {
+ return fmt.Errorf("insert pull repo: %w", err)
+ }
+ return nil
+ })
}
// ErrUserMustCollaborator represents an error that the user must be a collaborator to a given repo.
@@ -649,12 +633,6 @@ func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*P
return pulls, err
}
-// Update updates all fields of pull request.
-func (pr *PullRequest) Update(ctx context.Context) error {
- _, err := db.GetEngine(ctx).ID(pr.ID).AllCols().Update(pr)
- return err
-}
-
// UpdateCols updates specific fields of pull request.
func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error {
_, err := db.GetEngine(ctx).ID(pr.ID).Cols(cols...).Update(pr)
@@ -983,22 +961,18 @@ func TokenizeCodeOwnersLine(line string) []string {
// InsertPullRequests inserted pull requests
func InsertPullRequests(ctx context.Context, prs ...*PullRequest) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
- for _, pr := range prs {
- if err := insertIssue(ctx, pr.Issue); err != nil {
- return err
- }
- pr.IssueID = pr.Issue.ID
- if _, err := sess.NoAutoTime().Insert(pr); err != nil {
- return err
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ for _, pr := range prs {
+ if err := insertIssue(ctx, pr.Issue); err != nil {
+ return err
+ }
+ pr.IssueID = pr.Issue.ID
+ if _, err := db.GetEngine(ctx).NoAutoTime().Insert(pr); err != nil {
+ return err
+ }
}
- }
- return committer.Commit()
+ return nil
+ })
}
// GetPullRequestByMergedCommit returns a merged pull request by the given commit
diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go
index b685175f8e..84f9f6166d 100644
--- a/models/issues/pull_list.go
+++ b/models/issues/pull_list.go
@@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
applySorts(findSession, opts.SortType, 0)
findSession = db.SetSessionPagination(findSession, opts)
prs := make([]*PullRequest, 0, opts.PageSize)
- return prs, maxResults, findSession.Find(&prs)
+ found := findSession.Find(&prs)
+ return prs, maxResults, found
}
// PullRequestList defines a list of pull requests
diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go
index 8e09030215..39efaa5792 100644
--- a/models/issues/pull_test.go
+++ b/models/issues/pull_test.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestPullRequest_LoadAttributes(t *testing.T) {
@@ -76,6 +77,47 @@ func TestPullRequestsNewest(t *testing.T) {
}
}
+func TestPullRequests_Closed_RecentSortType(t *testing.T) {
+ // Issue ID | Closed At. | Updated At
+ // 2 | 1707270001 | 1707270001
+ // 3 | 1707271000 | 1707279999
+ // 11 | 1707279999 | 1707275555
+ tests := []struct {
+ sortType string
+ expectedIssueIDOrder []int64
+ }{
+ {"recentupdate", []int64{3, 11, 2}},
+ {"recentclose", []int64{11, 3, 2}},
+ }
+
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ _, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2")
+ require.NoError(t, err)
+ _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3")
+ require.NoError(t, err)
+ _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11")
+ require.NoError(t, err)
+
+ for _, test := range tests {
+ t.Run(test.sortType, func(t *testing.T) {
+ prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{
+ ListOptions: db.ListOptions{
+ Page: 1,
+ },
+ State: "closed",
+ SortType: test.sortType,
+ })
+ require.NoError(t, err)
+
+ if assert.Len(t, prs, len(test.expectedIssueIDOrder)) {
+ for i := range test.expectedIssueIDOrder {
+ assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID)
+ }
+ }
+ })
+ }
+}
+
func TestLoadRequestedReviewers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -206,19 +248,6 @@ func TestGetPullRequestByIssueID(t *testing.T) {
assert.True(t, issues_model.IsErrPullRequestNotExist(err))
}
-func TestPullRequest_Update(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1})
- pr.BaseBranch = "baseBranch"
- pr.HeadBranch = "headBranch"
- pr.Update(db.DefaultContext)
-
- pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
- assert.Equal(t, "baseBranch", pr.BaseBranch)
- assert.Equal(t, "headBranch", pr.HeadBranch)
- unittest.CheckConsistencyFor(t, pr)
-}
-
func TestPullRequest_UpdateCols(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
pr := &issues_model.PullRequest{
diff --git a/models/issues/reaction.go b/models/issues/reaction.go
index f24001fd23..3b5ad6d7ab 100644
--- a/models/issues/reaction.go
+++ b/models/issues/reaction.go
@@ -224,21 +224,9 @@ func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, erro
return nil, ErrForbiddenIssueReaction{opts.Type}
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- reaction, err := createReaction(ctx, opts)
- if err != nil {
- return reaction, err
- }
-
- if err := committer.Commit(); err != nil {
- return nil, err
- }
- return reaction, nil
+ return db.WithTx2(ctx, func(ctx context.Context) (*Reaction, error) {
+ return createReaction(ctx, opts)
+ })
}
// DeleteReaction deletes reaction for issue or comment.
diff --git a/models/issues/review.go b/models/issues/review.go
index 71fdb7456f..b758fa5ffa 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -334,54 +334,51 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
// CreateReview creates a new review based on opts
func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
-
- review := &Review{
- Issue: opts.Issue,
- IssueID: opts.Issue.ID,
- Reviewer: opts.Reviewer,
- ReviewerTeam: opts.ReviewerTeam,
- Content: opts.Content,
- Official: opts.Official,
- CommitID: opts.CommitID,
- Stale: opts.Stale,
- }
+ return db.WithTx2(ctx, func(ctx context.Context) (*Review, error) {
+ sess := db.GetEngine(ctx)
+
+ review := &Review{
+ Issue: opts.Issue,
+ IssueID: opts.Issue.ID,
+ Reviewer: opts.Reviewer,
+ ReviewerTeam: opts.ReviewerTeam,
+ Content: opts.Content,
+ Official: opts.Official,
+ CommitID: opts.CommitID,
+ Stale: opts.Stale,
+ }
- if opts.Reviewer != nil {
- review.Type = opts.Type
- review.ReviewerID = opts.Reviewer.ID
+ if opts.Reviewer != nil {
+ review.Type = opts.Type
+ review.ReviewerID = opts.Reviewer.ID
- reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID}
- // make sure user review requests are cleared
- if opts.Type != ReviewTypePending {
- if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
- return nil, err
+ reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID}
+ // make sure user review requests are cleared
+ if opts.Type != ReviewTypePending {
+ if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
+ return nil, err
+ }
}
- }
- // make sure if the created review gets dismissed no old review surface
- // other types can be ignored, as they don't affect branch protection
- if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject {
- if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))).
- Cols("dismissed").Update(&Review{Dismissed: true}); err != nil {
- return nil, err
+ // make sure if the created review gets dismissed no old review surface
+ // other types can be ignored, as they don't affect branch protection
+ if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject {
+ if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))).
+ Cols("dismissed").Update(&Review{Dismissed: true}); err != nil {
+ return nil, err
+ }
}
+ } else if opts.ReviewerTeam != nil {
+ review.Type = ReviewTypeRequest
+ review.ReviewerTeamID = opts.ReviewerTeam.ID
+ } else {
+ return nil, errors.New("provide either reviewer or reviewer team")
}
- } else if opts.ReviewerTeam != nil {
- review.Type = ReviewTypeRequest
- review.ReviewerTeamID = opts.ReviewerTeam.ID
- } else {
- return nil, errors.New("provide either reviewer or reviewer team")
- }
- if _, err := sess.Insert(review); err != nil {
- return nil, err
- }
- return review, committer.Commit()
+ if _, err := sess.Insert(review); err != nil {
+ return nil, err
+ }
+ return review, nil
+ })
}
// GetCurrentReview returns the current pending review of reviewer for given issue
@@ -605,168 +602,152 @@ func DismissReview(ctx context.Context, review *Review, isDismiss bool) (err err
// InsertReviews inserts review and review comments
func InsertReviews(ctx context.Context, reviews []*Review) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ sess := db.GetEngine(ctx)
- for _, review := range reviews {
- if _, err := sess.NoAutoTime().Insert(review); err != nil {
- return err
- }
+ for _, review := range reviews {
+ if _, err := sess.NoAutoTime().Insert(review); err != nil {
+ return err
+ }
- if _, err := sess.NoAutoTime().Insert(&Comment{
- Type: CommentTypeReview,
- Content: review.Content,
- PosterID: review.ReviewerID,
- OriginalAuthor: review.OriginalAuthor,
- OriginalAuthorID: review.OriginalAuthorID,
- IssueID: review.IssueID,
- ReviewID: review.ID,
- CreatedUnix: review.CreatedUnix,
- UpdatedUnix: review.UpdatedUnix,
- }); err != nil {
- return err
- }
+ if _, err := sess.NoAutoTime().Insert(&Comment{
+ Type: CommentTypeReview,
+ Content: review.Content,
+ PosterID: review.ReviewerID,
+ OriginalAuthor: review.OriginalAuthor,
+ OriginalAuthorID: review.OriginalAuthorID,
+ IssueID: review.IssueID,
+ ReviewID: review.ID,
+ CreatedUnix: review.CreatedUnix,
+ UpdatedUnix: review.UpdatedUnix,
+ }); err != nil {
+ return err
+ }
- for _, c := range review.Comments {
- c.ReviewID = review.ID
- }
+ for _, c := range review.Comments {
+ c.ReviewID = review.ID
+ }
- if len(review.Comments) > 0 {
- if _, err := sess.NoAutoTime().Insert(review.Comments); err != nil {
- return err
+ if len(review.Comments) > 0 {
+ if _, err := sess.NoAutoTime().Insert(review.Comments); err != nil {
+ return err
+ }
}
- }
- if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
- return err
+ if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
+ return err
+ }
}
- }
-
- return committer.Commit()
+ return nil
+ })
}
// AddReviewRequest add a review request from one reviewer
func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
- sess := db.GetEngine(ctx)
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ sess := db.GetEngine(ctx)
- review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
- if err != nil && !IsErrReviewNotExist(err) {
- return nil, err
- }
-
- if review != nil {
- // skip it when reviewer has been request to review
- if review.Type == ReviewTypeRequest {
- return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
- }
-
- if issue.IsClosed {
- return nil, ErrReviewRequestOnClosedPR{}
+ review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
+ if err != nil && !IsErrReviewNotExist(err) {
+ return nil, err
}
- if issue.IsPull {
- if err := issue.LoadPullRequest(ctx); err != nil {
- return nil, err
+ if review != nil {
+ // skip it when reviewer has been request to review
+ if review.Type == ReviewTypeRequest {
+ return nil, nil // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
}
- if issue.PullRequest.HasMerged {
+
+ if issue.IsClosed {
return nil, ErrReviewRequestOnClosedPR{}
}
+
+ if issue.IsPull {
+ if err := issue.LoadPullRequest(ctx); err != nil {
+ return nil, err
+ }
+ if issue.PullRequest.HasMerged {
+ return nil, ErrReviewRequestOnClosedPR{}
+ }
+ }
}
- }
- // if the reviewer is an official reviewer,
- // remove the official flag in the all previous reviews
- official, err := IsOfficialReviewer(ctx, issue, reviewer)
- if err != nil {
- return nil, err
- } else if official {
- if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, reviewer.ID); err != nil {
+ // if the reviewer is an official reviewer,
+ // remove the official flag in the all previous reviews
+ official, err := IsOfficialReviewer(ctx, issue, reviewer)
+ if err != nil {
return nil, err
+ } else if official {
+ if _, err := sess.Exec("UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_id=?", false, issue.ID, reviewer.ID); err != nil {
+ return nil, err
+ }
}
- }
- review, err = CreateReview(ctx, CreateReviewOptions{
- Type: ReviewTypeRequest,
- Issue: issue,
- Reviewer: reviewer,
- Official: official,
- Stale: false,
- })
- if err != nil {
- return nil, err
- }
+ review, err = CreateReview(ctx, CreateReviewOptions{
+ Type: ReviewTypeRequest,
+ Issue: issue,
+ Reviewer: reviewer,
+ Official: official,
+ Stale: false,
+ })
+ if err != nil {
+ return nil, err
+ }
- comment, err := CreateComment(ctx, &CreateCommentOptions{
- Type: CommentTypeReviewRequest,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- RemovedAssignee: false, // Use RemovedAssignee as !isRequest
- AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID
- ReviewID: review.ID,
- })
- if err != nil {
- return nil, err
- }
+ comment, err := CreateComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeReviewRequest,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ RemovedAssignee: false, // Use RemovedAssignee as !isRequest
+ AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID
+ ReviewID: review.ID,
+ })
+ if err != nil {
+ return nil, err
+ }
- // func caller use the created comment to retrieve created review too.
- comment.Review = review
+ // func caller use the created comment to retrieve created review too.
+ comment.Review = review
- return comment, committer.Commit()
+ return comment, nil
+ })
}
// RemoveReviewRequest remove a review request from one reviewer
func RemoveReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_model.User) (*Comment, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
- if err != nil && !IsErrReviewNotExist(err) {
- return nil, err
- }
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID)
+ if err != nil && !IsErrReviewNotExist(err) {
+ return nil, err
+ }
- if review == nil || review.Type != ReviewTypeRequest {
- return nil, nil
- }
+ if review == nil || review.Type != ReviewTypeRequest {
+ return nil, nil
+ }
- if _, err = db.DeleteByBean(ctx, review); err != nil {
- return nil, err
- }
+ if _, err = db.DeleteByBean(ctx, review); err != nil {
+ return nil, err
+ }
- official, err := IsOfficialReviewer(ctx, issue, reviewer)
- if err != nil {
- return nil, err
- } else if official {
- if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil {
+ official, err := IsOfficialReviewer(ctx, issue, reviewer)
+ if err != nil {
return nil, err
+ } else if official {
+ if err := restoreLatestOfficialReview(ctx, issue.ID, reviewer.ID); err != nil {
+ return nil, err
+ }
}
- }
- comment, err := CreateComment(ctx, &CreateCommentOptions{
- Type: CommentTypeReviewRequest,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- RemovedAssignee: true, // Use RemovedAssignee as !isRequest
- AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID
+ return CreateComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeReviewRequest,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ RemovedAssignee: true, // Use RemovedAssignee as !isRequest
+ AssigneeID: reviewer.ID, // Use AssigneeID as reviewer ID
+ })
})
- if err != nil {
- return nil, err
- }
-
- return comment, committer.Commit()
}
// Recalculate the latest official review for reviewer
@@ -787,120 +768,112 @@ func restoreLatestOfficialReview(ctx context.Context, issueID, reviewerID int64)
// AddTeamReviewRequest add a review request from one team
func AddTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID)
+ if err != nil && !IsErrReviewNotExist(err) {
+ return nil, err
+ }
- review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID)
- if err != nil && !IsErrReviewNotExist(err) {
- return nil, err
- }
+ // This team already has been requested to review - therefore skip this.
+ if review != nil {
+ return nil, nil
+ }
- // This team already has been requested to review - therefore skip this.
- if review != nil {
- return nil, nil
- }
+ official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
+ if err != nil {
+ return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
+ } else if !official {
+ if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil {
+ return nil, fmt.Errorf("isOfficialReviewer(): %w", err)
+ }
+ }
- official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
- if err != nil {
- return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
- } else if !official {
- if official, err = IsOfficialReviewer(ctx, issue, doer); err != nil {
- return nil, fmt.Errorf("isOfficialReviewer(): %w", err)
+ if review, err = CreateReview(ctx, CreateReviewOptions{
+ Type: ReviewTypeRequest,
+ Issue: issue,
+ ReviewerTeam: reviewer,
+ Official: official,
+ Stale: false,
+ }); err != nil {
+ return nil, err
}
- }
- if review, err = CreateReview(ctx, CreateReviewOptions{
- Type: ReviewTypeRequest,
- Issue: issue,
- ReviewerTeam: reviewer,
- Official: official,
- Stale: false,
- }); err != nil {
- return nil, err
- }
+ if official {
+ if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?", false, issue.ID, reviewer.ID); err != nil {
+ return nil, err
+ }
+ }
- if official {
- if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE issue_id=? AND reviewer_team_id=?", false, issue.ID, reviewer.ID); err != nil {
- return nil, err
+ comment, err := CreateComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeReviewRequest,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ RemovedAssignee: false, // Use RemovedAssignee as !isRequest
+ AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
+ ReviewID: review.ID,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("CreateComment(): %w", err)
}
- }
- comment, err := CreateComment(ctx, &CreateCommentOptions{
- Type: CommentTypeReviewRequest,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- RemovedAssignee: false, // Use RemovedAssignee as !isRequest
- AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
- ReviewID: review.ID,
+ return comment, nil
})
- if err != nil {
- return nil, fmt.Errorf("CreateComment(): %w", err)
- }
-
- return comment, committer.Commit()
}
// RemoveTeamReviewRequest remove a review request from one team
func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organization.Team, doer *user_model.User) (*Comment, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID)
- if err != nil && !IsErrReviewNotExist(err) {
- return nil, err
- }
-
- if review == nil {
- return nil, nil
- }
-
- if _, err = db.DeleteByBean(ctx, review); err != nil {
- return nil, err
- }
+ return db.WithTx2(ctx, func(ctx context.Context) (*Comment, error) {
+ review, err := GetTeamReviewerByIssueIDAndTeamID(ctx, issue.ID, reviewer.ID)
+ if err != nil && !IsErrReviewNotExist(err) {
+ return nil, err
+ }
- official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
- if err != nil {
- return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
- }
+ if review == nil {
+ return nil, nil
+ }
- if official {
- // recalculate which is the latest official review from that team
- review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID)
- if err != nil && !IsErrReviewNotExist(err) {
+ if _, err = db.DeleteByBean(ctx, review); err != nil {
return nil, err
}
- if review != nil {
- if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
+ official, err := IsOfficialReviewerTeam(ctx, issue, reviewer)
+ if err != nil {
+ return nil, fmt.Errorf("isOfficialReviewerTeam(): %w", err)
+ }
+
+ if official {
+ // recalculate which is the latest official review from that team
+ review, err := GetReviewByIssueIDAndUserID(ctx, issue.ID, -reviewer.ID)
+ if err != nil && !IsErrReviewNotExist(err) {
return nil, err
}
+
+ if review != nil {
+ if _, err := db.Exec(ctx, "UPDATE `review` SET official=? WHERE id=?", true, review.ID); err != nil {
+ return nil, err
+ }
+ }
}
- }
- if doer == nil {
- return nil, committer.Commit()
- }
+ if doer == nil {
+ return nil, nil
+ }
- comment, err := CreateComment(ctx, &CreateCommentOptions{
- Type: CommentTypeReviewRequest,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- RemovedAssignee: true, // Use RemovedAssignee as !isRequest
- AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
- })
- if err != nil {
- return nil, fmt.Errorf("CreateComment(): %w", err)
- }
+ comment, err := CreateComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeReviewRequest,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ RemovedAssignee: true, // Use RemovedAssignee as !isRequest
+ AssigneeTeamID: reviewer.ID, // Use AssigneeTeamID as reviewer team ID
+ })
+ if err != nil {
+ return nil, fmt.Errorf("CreateComment(): %w", err)
+ }
- return comment, committer.Commit()
+ return comment, nil
+ })
}
// MarkConversation Add or remove Conversation mark for a code comment
@@ -966,61 +939,56 @@ func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.Use
// DeleteReview delete a review and it's code comments
func DeleteReview(ctx context.Context, r *Review) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if r.ID == 0 {
- return errors.New("review is not allowed to be 0")
- }
-
- if r.Type == ReviewTypeRequest {
- return errors.New("review request can not be deleted using this method")
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if r.ID == 0 {
+ return errors.New("review is not allowed to be 0")
+ }
- opts := FindCommentsOptions{
- Type: CommentTypeCode,
- IssueID: r.IssueID,
- ReviewID: r.ID,
- }
+ if r.Type == ReviewTypeRequest {
+ return errors.New("review request can not be deleted using this method")
+ }
- if _, err := db.Delete[Comment](ctx, opts); err != nil {
- return err
- }
+ opts := FindCommentsOptions{
+ Type: CommentTypeCode,
+ IssueID: r.IssueID,
+ ReviewID: r.ID,
+ }
- opts = FindCommentsOptions{
- Type: CommentTypeReview,
- IssueID: r.IssueID,
- ReviewID: r.ID,
- }
+ if _, err := db.Delete[Comment](ctx, opts); err != nil {
+ return err
+ }
- if _, err := db.Delete[Comment](ctx, opts); err != nil {
- return err
- }
+ opts = FindCommentsOptions{
+ Type: CommentTypeReview,
+ IssueID: r.IssueID,
+ ReviewID: r.ID,
+ }
- opts = FindCommentsOptions{
- Type: CommentTypeDismissReview,
- IssueID: r.IssueID,
- ReviewID: r.ID,
- }
+ if _, err := db.Delete[Comment](ctx, opts); err != nil {
+ return err
+ }
- if _, err := db.Delete[Comment](ctx, opts); err != nil {
- return err
- }
+ opts = FindCommentsOptions{
+ Type: CommentTypeDismissReview,
+ IssueID: r.IssueID,
+ ReviewID: r.ID,
+ }
- if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
- return err
- }
+ if _, err := db.Delete[Comment](ctx, opts); err != nil {
+ return err
+ }
- if r.Official {
- if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
+ if _, err := db.DeleteByID[Review](ctx, r.ID); err != nil {
return err
}
- }
- return committer.Commit()
+ if r.Official {
+ if err := restoreLatestOfficialReview(ctx, r.IssueID, r.ReviewerID); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
}
// GetCodeCommentsCount return count of CodeComments a Review has
diff --git a/models/issues/review_list.go b/models/issues/review_list.go
index 928f24fb2d..bbb8c489fa 100644
--- a/models/issues/review_list.go
+++ b/models/issues/review_list.go
@@ -22,7 +22,7 @@ type ReviewList []*Review
// LoadReviewers loads reviewers
func (reviews ReviewList) LoadReviewers(ctx context.Context) error {
reviewerIDs := make([]int64, len(reviews))
- for i := 0; i < len(reviews); i++ {
+ for i := range reviews {
reviewerIDs[i] = reviews[i].ReviewerID
}
reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs)
diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go
index 7c05a3a883..761b8f91a0 100644
--- a/models/issues/stopwatch.go
+++ b/models/issues/stopwatch.go
@@ -5,7 +5,6 @@ package issues
import (
"context"
- "fmt"
"time"
"code.gitea.io/gitea/models/db"
@@ -15,20 +14,6 @@ import (
"code.gitea.io/gitea/modules/util"
)
-// ErrIssueStopwatchNotExist represents an error that stopwatch is not exist
-type ErrIssueStopwatchNotExist struct {
- UserID int64
- IssueID int64
-}
-
-func (err ErrIssueStopwatchNotExist) Error() string {
- return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
-}
-
-func (err ErrIssueStopwatchNotExist) Unwrap() error {
- return util.ErrNotExist
-}
-
// Stopwatch represents a stopwatch for time tracking.
type Stopwatch struct {
ID int64 `xorm:"pk autoincr"`
@@ -55,13 +40,11 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex
return sw, exists, err
}
-// UserIDCount is a simple coalition of UserID and Count
type UserStopwatch struct {
UserID int64
StopWatches []*Stopwatch
}
-// GetUIDsAndNotificationCounts between the two provided times
func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
sws := []*Stopwatch{}
if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil {
@@ -87,7 +70,7 @@ func GetUIDsAndStopwatch(ctx context.Context) ([]*UserStopwatch, error) {
return res, nil
}
-// GetUserStopwatches return list of all stopwatches of a user
+// GetUserStopwatches return list of the user's all stopwatches
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
sws := make([]*Stopwatch, 0, 8)
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
@@ -102,7 +85,7 @@ func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOp
return sws, nil
}
-// CountUserStopwatches return count of all stopwatches of a user
+// CountUserStopwatches return count of the user's all stopwatches
func CountUserStopwatches(ctx context.Context, userID int64) (int64, error) {
return db.GetEngine(ctx).Where("user_id = ?", userID).Count(&Stopwatch{})
}
@@ -136,43 +119,21 @@ func HasUserStopwatch(ctx context.Context, userID int64) (exists bool, sw *Stopw
return exists, sw, issue, err
}
-// FinishIssueStopwatchIfPossible if stopwatch exist then finish it otherwise ignore
-func FinishIssueStopwatchIfPossible(ctx context.Context, user *user_model.User, issue *Issue) error {
- _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
- if !exists {
- return nil
- }
- return FinishIssueStopwatch(ctx, user, issue)
-}
-
-// CreateOrStopIssueStopwatch create an issue stopwatch if it's not exist, otherwise finish it
-func CreateOrStopIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- _, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
- if exists {
- return FinishIssueStopwatch(ctx, user, issue)
- }
- return CreateIssueStopwatch(ctx, user, issue)
-}
-
-// FinishIssueStopwatch if stopwatch exist then finish it otherwise return an error
-func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
+// FinishIssueStopwatch if stopwatch exists, then finish it.
+func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
if err != nil {
- return err
+ return false, err
+ } else if !exists {
+ return false, nil
}
- if !exists {
- return ErrIssueStopwatchNotExist{
- UserID: user.ID,
- IssueID: issue.ID,
- }
+ if err = finishIssueStopwatch(ctx, user, issue, sw); err != nil {
+ return false, err
}
+ return true, nil
+}
+func finishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue, sw *Stopwatch) error {
// Create tracked time out of the time difference between start date and actual date
timediff := time.Now().Unix() - int64(sw.CreatedUnix)
@@ -184,14 +145,12 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
Time: timediff,
}
- if err := db.Insert(ctx, tt); err != nil {
+ if err := issue.LoadRepo(ctx); err != nil {
return err
}
-
- if err := issue.LoadRepo(ctx); err != nil {
+ if err := db.Insert(ctx, tt); err != nil {
return err
}
-
if _, err := CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
@@ -202,83 +161,65 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
}); err != nil {
return err
}
- _, err = db.DeleteByBean(ctx, sw)
+ _, err := db.DeleteByBean(ctx, sw)
return err
}
-// CreateIssueStopwatch creates a stopwatch if not exist, otherwise return an error
-func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- }
-
- // if another stopwatch is running: stop it
- exists, _, otherIssue, err := HasUserStopwatch(ctx, user.ID)
- if err != nil {
- return err
- }
- if exists {
- if err := FinishIssueStopwatch(ctx, user, otherIssue); err != nil {
- return err
+// CreateIssueStopwatch creates a stopwatch if the issue doesn't have the user's stopwatch.
+// It also stops any other stopwatch that might be running for the user.
+func CreateIssueStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
+ { // if another issue's stopwatch is running: stop it; if this issue has a stopwatch: return an error.
+ exists, otherStopWatch, otherIssue, err := HasUserStopwatch(ctx, user.ID)
+ if err != nil {
+ return false, err
+ }
+ if exists {
+ if otherStopWatch.IssueID == issue.ID {
+ // don't allow starting stopwatch for the same issue
+ return false, nil
+ }
+ // stop the other issue's stopwatch
+ if err = finishIssueStopwatch(ctx, user, otherIssue, otherStopWatch); err != nil {
+ return false, err
+ }
}
}
- // Create stopwatch
- sw := &Stopwatch{
- UserID: user.ID,
- IssueID: issue.ID,
+ if err = issue.LoadRepo(ctx); err != nil {
+ return false, err
}
-
- if err := db.Insert(ctx, sw); err != nil {
- return err
+ if err = db.Insert(ctx, &Stopwatch{UserID: user.ID, IssueID: issue.ID}); err != nil {
+ return false, err
}
-
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- }
-
- if _, err := CreateComment(ctx, &CreateCommentOptions{
+ if _, err = CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
Repo: issue.Repo,
Type: CommentTypeStartTracking,
}); err != nil {
- return err
+ return false, err
}
-
- return nil
+ return true, nil
}
// CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
-func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
- if err := cancelStopwatch(ctx, user, issue); err != nil {
- return err
- }
- return committer.Commit()
-}
-
-func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) error {
- e := db.GetEngine(ctx)
- sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
- if err != nil {
- return err
- }
-
- if exists {
- if _, err := e.Delete(sw); err != nil {
+func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (ok bool, err error) {
+ err = db.WithTx(ctx, func(ctx context.Context) error {
+ e := db.GetEngine(ctx)
+ sw, exists, err := getStopwatch(ctx, user.ID, issue.ID)
+ if err != nil {
return err
+ } else if !exists {
+ return nil
}
- if err := issue.LoadRepo(ctx); err != nil {
+ if err = issue.LoadRepo(ctx); err != nil {
return err
}
-
- if _, err := CreateComment(ctx, &CreateCommentOptions{
+ if _, err = e.Delete(sw); err != nil {
+ return err
+ }
+ if _, err = CreateComment(ctx, &CreateCommentOptions{
Doer: user,
Issue: issue,
Repo: issue.Repo,
@@ -286,6 +227,8 @@ func cancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) e
}); err != nil {
return err
}
- }
- return nil
+ ok = true
+ return nil
+ })
+ return ok, err
}
diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go
index a1bf9dc931..6333c10234 100644
--- a/models/issues/stopwatch_test.go
+++ b/models/issues/stopwatch_test.go
@@ -10,7 +10,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
)
@@ -18,26 +17,22 @@ import (
func TestCancelStopwatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- user1, err := user_model.GetUserByID(db.DefaultContext, 1)
- assert.NoError(t, err)
-
- issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
- assert.NoError(t, err)
- issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2)
- assert.NoError(t, err)
+ user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+ issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
- err = issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
+ ok, err := issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
assert.NoError(t, err)
+ assert.True(t, ok)
unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user1.ID, IssueID: issue1.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
- _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID})
-
- assert.NoError(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2))
+ ok, err = issues_model.CancelStopwatch(db.DefaultContext, user1, issue1)
+ assert.NoError(t, err)
+ assert.False(t, ok)
}
func TestStopwatchExists(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
-
assert.True(t, issues_model.StopwatchExists(db.DefaultContext, 1, 1))
assert.False(t, issues_model.StopwatchExists(db.DefaultContext, 1, 2))
}
@@ -58,21 +53,35 @@ func TestHasUserStopwatch(t *testing.T) {
func TestCreateOrStopIssueStopwatch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- user2, err := user_model.GetUserByID(db.DefaultContext, 2)
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
+ issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
+ issue3 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
+
+ // create a new stopwatch
+ ok, err := issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue1)
assert.NoError(t, err)
- org3, err := user_model.GetUserByID(db.DefaultContext, 3)
+ assert.True(t, ok)
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue1.ID})
+ // should not create a second stopwatch for the same issue
+ ok, err = issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue1)
assert.NoError(t, err)
-
- issue1, err := issues_model.GetIssueByID(db.DefaultContext, 1)
+ assert.False(t, ok)
+ // on a different issue, it will finish the existing stopwatch and create a new one
+ ok, err = issues_model.CreateIssueStopwatch(db.DefaultContext, user4, issue3)
assert.NoError(t, err)
- issue2, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.True(t, ok)
+ unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue1.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: user4.ID, IssueID: issue3.ID})
+
+ // user2 already has a stopwatch in test fixture
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
+ ok, err = issues_model.FinishIssueStopwatch(db.DefaultContext, user2, issue2)
assert.NoError(t, err)
-
- assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, org3, issue1))
- sw := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: 3, IssueID: 1})
- assert.LessOrEqual(t, sw.CreatedUnix, timeutil.TimeStampNow())
-
- assert.NoError(t, issues_model.CreateOrStopIssueStopwatch(db.DefaultContext, user2, issue2))
- unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: 2, IssueID: 2})
- unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: 2, IssueID: 2})
+ assert.True(t, ok)
+ unittest.AssertNotExistsBean(t, &issues_model.Stopwatch{UserID: user2.ID, IssueID: issue2.ID})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.TrackedTime{UserID: user2.ID, IssueID: issue2.ID})
+ ok, err = issues_model.FinishIssueStopwatch(db.DefaultContext, user2, issue2)
+ assert.NoError(t, err)
+ assert.False(t, ok)
}
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index ea404d36cd..9c11881e44 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -168,35 +168,31 @@ func GetTrackedSeconds(ctx context.Context, opts FindTrackedTimesOptions) (track
// AddTime will add the given time (in seconds) to the issue
func AddTime(ctx context.Context, user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return nil, err
- }
- defer committer.Close()
-
- t, err := addTime(ctx, user, issue, amount, created)
- if err != nil {
- return nil, err
- }
+ return db.WithTx2(ctx, func(ctx context.Context) (*TrackedTime, error) {
+ t, err := addTime(ctx, user, issue, amount, created)
+ if err != nil {
+ return nil, err
+ }
- if err := issue.LoadRepo(ctx); err != nil {
- return nil, err
- }
+ if err := issue.LoadRepo(ctx); err != nil {
+ return nil, err
+ }
- if _, err := CreateComment(ctx, &CreateCommentOptions{
- Issue: issue,
- Repo: issue.Repo,
- Doer: user,
- // Content before v1.21 did store the formatted string instead of seconds,
- // so use "|" as delimiter to mark the new format
- Content: fmt.Sprintf("|%d", amount),
- Type: CommentTypeAddTimeManual,
- TimeID: t.ID,
- }); err != nil {
- return nil, err
- }
+ if _, err := CreateComment(ctx, &CreateCommentOptions{
+ Issue: issue,
+ Repo: issue.Repo,
+ Doer: user,
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
+ Content: fmt.Sprintf("|%d", amount),
+ Type: CommentTypeAddTimeManual,
+ TimeID: t.ID,
+ }); err != nil {
+ return nil, err
+ }
- return t, committer.Commit()
+ return t, nil
+ })
}
func addTime(ctx context.Context, user *user_model.User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
@@ -241,72 +237,58 @@ func TotalTimesForEachUser(ctx context.Context, options *FindTrackedTimesOptions
// DeleteIssueUserTimes deletes times for issue
func DeleteIssueUserTimes(ctx context.Context, issue *Issue, user *user_model.User) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- opts := FindTrackedTimesOptions{
- IssueID: issue.ID,
- UserID: user.ID,
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ opts := FindTrackedTimesOptions{
+ IssueID: issue.ID,
+ UserID: user.ID,
+ }
- removedTime, err := deleteTimes(ctx, opts)
- if err != nil {
- return err
- }
- if removedTime == 0 {
- return db.ErrNotExist{Resource: "tracked_time"}
- }
+ removedTime, err := deleteTimes(ctx, opts)
+ if err != nil {
+ return err
+ }
+ if removedTime == 0 {
+ return db.ErrNotExist{Resource: "tracked_time"}
+ }
- if err := issue.LoadRepo(ctx); err != nil {
- return err
- }
- if _, err := CreateComment(ctx, &CreateCommentOptions{
- Issue: issue,
- Repo: issue.Repo,
- Doer: user,
- // Content before v1.21 did store the formatted string instead of seconds,
- // so use "|" as delimiter to mark the new format
- Content: fmt.Sprintf("|%d", removedTime),
- Type: CommentTypeDeleteTimeManual,
- }); err != nil {
+ if err := issue.LoadRepo(ctx); err != nil {
+ return err
+ }
+ _, err = CreateComment(ctx, &CreateCommentOptions{
+ Issue: issue,
+ Repo: issue.Repo,
+ Doer: user,
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
+ Content: fmt.Sprintf("|%d", removedTime),
+ Type: CommentTypeDeleteTimeManual,
+ })
return err
- }
-
- return committer.Commit()
+ })
}
// DeleteTime delete a specific Time
func DeleteTime(ctx context.Context, t *TrackedTime) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := t.LoadAttributes(ctx); err != nil {
- return err
- }
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ if err := t.LoadAttributes(ctx); err != nil {
+ return err
+ }
- if err := deleteTime(ctx, t); err != nil {
- return err
- }
+ if err := deleteTime(ctx, t); err != nil {
+ return err
+ }
- if _, err := CreateComment(ctx, &CreateCommentOptions{
- Issue: t.Issue,
- Repo: t.Issue.Repo,
- Doer: t.User,
- // Content before v1.21 did store the formatted string instead of seconds,
- // so use "|" as delimiter to mark the new format
- Content: fmt.Sprintf("|%d", t.Time),
- Type: CommentTypeDeleteTimeManual,
- }); err != nil {
+ _, err := CreateComment(ctx, &CreateCommentOptions{
+ Issue: t.Issue,
+ Repo: t.Issue.Repo,
+ Doer: t.User,
+ // Content before v1.21 did store the formatted string instead of seconds,
+ // so use "|" as delimiter to mark the new format
+ Content: fmt.Sprintf("|%d", t.Time),
+ Type: CommentTypeDeleteTimeManual,
+ })
return err
- }
-
- return committer.Commit()
+ })
}
func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime int64, err error) {
@@ -350,10 +332,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed
// we get the statistics in smaller chunks and get accumulates
var accum int64
for i := 0; i < len(opts.IssueIDs); {
- chunk := i + MaxQueryParameters
- if chunk > len(opts.IssueIDs) {
- chunk = len(opts.IssueIDs)
- }
+ chunk := min(i+MaxQueryParameters, len(opts.IssueIDs))
time, err := getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs[i:chunk])
if err != nil {
return 0, err