mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
199 lines
5.7 KiB
Go
199 lines
5.7 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package issue
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/hanzoai/git/models/db"
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
access_model "github.com/hanzoai/git/models/perm/access"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/gitrepo"
|
|
"github.com/hanzoai/git/modules/json"
|
|
"github.com/hanzoai/git/modules/log"
|
|
"github.com/hanzoai/git/modules/timeutil"
|
|
git_service "github.com/hanzoai/git/services/git"
|
|
notify_service "github.com/hanzoai/git/services/notify"
|
|
|
|
"github.com/hanzoai/builder"
|
|
)
|
|
|
|
// CreateRefComment creates a commit reference comment to issue.
|
|
func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, commitSHA string) error {
|
|
if len(commitSHA) == 0 {
|
|
return errors.New("cannot create reference with empty commit SHA")
|
|
}
|
|
|
|
if user_model.IsUserBlockedBy(ctx, doer, issue.PosterID, repo.OwnerID) {
|
|
if isAdmin, _ := access_model.IsUserRepoAdmin(ctx, repo, doer); !isAdmin {
|
|
return user_model.ErrBlockedUser
|
|
}
|
|
}
|
|
|
|
// Check if same reference from same commit has already existed.
|
|
has, err := db.Exist[issues_model.Comment](ctx, builder.Eq{
|
|
"`type`": issues_model.CommentTypeCommitRef,
|
|
"issue_id": issue.ID,
|
|
"commit_sha": commitSHA,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("check reference comment: %w", err)
|
|
} else if has {
|
|
return nil
|
|
}
|
|
|
|
_, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
|
|
Type: issues_model.CommentTypeCommitRef,
|
|
Doer: doer,
|
|
Repo: repo,
|
|
Issue: issue,
|
|
CommitSHA: commitSHA,
|
|
Content: content,
|
|
})
|
|
return err
|
|
}
|
|
|
|
// CreateIssueComment creates a plain issue comment.
|
|
func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content string, attachments []string) (*issues_model.Comment, error) {
|
|
if user_model.IsUserBlockedBy(ctx, doer, issue.PosterID, repo.OwnerID) {
|
|
if isAdmin, _ := access_model.IsUserRepoAdmin(ctx, repo, doer); !isAdmin {
|
|
return nil, user_model.ErrBlockedUser
|
|
}
|
|
}
|
|
|
|
comment, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
|
|
Type: issues_model.CommentTypeComment,
|
|
Doer: doer,
|
|
Repo: repo,
|
|
Issue: issue,
|
|
Content: content,
|
|
Attachments: attachments,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, doer, comment.Content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// reload issue to ensure it has the latest data, especially the number of comments
|
|
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
notify_service.CreateIssueComment(ctx, doer, repo, issue, comment, mentions)
|
|
|
|
return comment, nil
|
|
}
|
|
|
|
// UpdateComment updates information of comment.
|
|
func UpdateComment(ctx context.Context, c *issues_model.Comment, contentVersion int, doer *user_model.User, oldContent string) error {
|
|
if err := c.LoadIssue(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.Issue.LoadRepo(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if user_model.IsUserBlockedBy(ctx, doer, c.Issue.PosterID, c.Issue.Repo.OwnerID) {
|
|
if isAdmin, _ := access_model.IsUserRepoAdmin(ctx, c.Issue.Repo, doer); !isAdmin {
|
|
return user_model.ErrBlockedUser
|
|
}
|
|
}
|
|
|
|
needsContentHistory := c.Content != oldContent && c.Type.HasContentSupport()
|
|
if needsContentHistory {
|
|
hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, c.IssueID, c.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasContentHistory {
|
|
if err = issues_model.SaveIssueContentHistory(ctx, c.PosterID, c.IssueID, c.ID,
|
|
c.CreatedUnix, oldContent, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := issues_model.UpdateComment(ctx, c, contentVersion, doer); err != nil {
|
|
return err
|
|
}
|
|
|
|
if needsContentHistory {
|
|
err := issues_model.SaveIssueContentHistory(ctx, doer.ID, c.IssueID, c.ID, timeutil.TimeStampNow(), c.Content, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
notify_service.UpdateComment(ctx, doer, c, oldContent)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteComment deletes the comment
|
|
func DeleteComment(ctx context.Context, doer *user_model.User, comment *issues_model.Comment) error {
|
|
err := db.WithTx(ctx, func(ctx context.Context) error {
|
|
return issues_model.DeleteComment(ctx, comment)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
notify_service.DeleteComment(ctx, doer, comment)
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadCommentPushCommits Load push commits
|
|
func LoadCommentPushCommits(ctx context.Context, c *issues_model.Comment) error {
|
|
if c.Content == "" || c.Commits != nil || c.Type != issues_model.CommentTypePullRequestPush {
|
|
return nil
|
|
}
|
|
|
|
var data issues_model.PushActionContent
|
|
if err := json.Unmarshal([]byte(c.Content), &data); err != nil {
|
|
log.Debug("Unmarshal: %v", err) // no need to show 500 error to end user when the JSON is broken
|
|
return nil
|
|
}
|
|
|
|
c.IsForcePush = data.IsForcePush
|
|
|
|
if c.IsForcePush {
|
|
if len(data.CommitIDs) != 2 {
|
|
return nil
|
|
}
|
|
c.OldCommit, c.NewCommit = data.CommitIDs[0], data.CommitIDs[1]
|
|
} else {
|
|
if err := c.LoadIssue(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := c.Issue.LoadRepo(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer.Close()
|
|
|
|
c.Commits, err = git_service.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo, "") // no current ref sub path for PR commit list
|
|
if err != nil {
|
|
log.Debug("ConvertFromGitCommit: %v", err) // no need to show 500 error to end user when the commit does not exist
|
|
} else {
|
|
c.CommitsNum = int64(len(c.Commits))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|