gitea/models/repo/commit_comment.go

195 lines
5.6 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"fmt"
"html/template"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
// CommitComment represents a comment on a specific line in a commit diff
type CommitComment struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX"`
Repo *Repository `xorm:"-"`
CommitSHA string `xorm:"VARCHAR(64) INDEX"`
TreePath string `xorm:"VARCHAR(4000)"` // File path (same field name as issue comments)
Line int64 // - previous line / + proposed line
Content string `xorm:"LONGTEXT"`
ContentVersion int `xorm:"NOT NULL DEFAULT 0"`
RenderedContent template.HTML `xorm:"-"`
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Attachments []*Attachment `xorm:"-"`
// Fields for template compatibility with PR comments
Review any `xorm:"-"` // Always nil for commit comments
Invalidated bool `xorm:"-"` // Always false for commit comments
ResolveDoer any `xorm:"-"` // Always nil for commit comments
Reactions any `xorm:"-"` // Reactions for this comment
}
// IsResolved returns false (commit comments don't support resolution)
func (c *CommitComment) IsResolved() bool {
return false
}
// HasOriginalAuthor returns if a comment was migrated and has an original author
func (c *CommitComment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
}
func init() {
db.RegisterModel(new(CommitComment))
}
// ErrCommitCommentNotExist represents a "CommitCommentNotExist" kind of error.
type ErrCommitCommentNotExist struct {
ID int64
}
// IsErrCommitCommentNotExist checks if an error is a ErrCommitCommentNotExist.
func IsErrCommitCommentNotExist(err error) bool {
_, ok := err.(ErrCommitCommentNotExist)
return ok
}
func (err ErrCommitCommentNotExist) Error() string {
return fmt.Sprintf("commit comment does not exist [id: %d]", err.ID)
}
// CreateCommitComment creates a new commit comment
func CreateCommitComment(ctx context.Context, comment *CommitComment) error {
return db.Insert(ctx, comment)
}
// GetCommitCommentByID returns a commit comment by ID
func GetCommitCommentByID(ctx context.Context, id int64) (*CommitComment, error) {
comment := new(CommitComment)
has, err := db.GetEngine(ctx).ID(id).Get(comment)
if err != nil {
return nil, err
} else if !has {
return nil, ErrCommitCommentNotExist{id}
}
return comment, nil
}
// FindCommitCommentsOptions describes the conditions to find commit comments
type FindCommitCommentsOptions struct {
db.ListOptions
RepoID int64
CommitSHA string
Path string
Line int64
}
// ToConds implements FindOptions interface
func (opts FindCommitCommentsOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
}
if opts.Path != "" {
cond = cond.And(builder.Eq{"tree_path": opts.Path})
}
if opts.Line != 0 {
cond = cond.And(builder.Eq{"line": opts.Line})
}
return cond
}
// FindCommitComments returns commit comments based on options
func FindCommitComments(ctx context.Context, opts FindCommitCommentsOptions) ([]*CommitComment, error) {
comments := make([]*CommitComment, 0, 10)
sess := db.GetEngine(ctx).Where(opts.ToConds())
if opts.Page > 0 {
sess = db.SetSessionPagination(sess, &opts)
}
return comments, sess.Find(&comments)
}
// LoadPoster loads the poster user
func (c *CommitComment) LoadPoster(ctx context.Context) error {
if c.Poster != nil {
return nil
}
var err error
c.Poster, err = user_model.GetPossibleUserByID(ctx, c.PosterID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
c.PosterID = user_model.GhostUserID
c.Poster = user_model.NewGhostUser()
}
}
return err
}
// LoadRepo loads the repository
func (c *CommitComment) LoadRepo(ctx context.Context) error {
if c.Repo != nil {
return nil
}
var err error
c.Repo, err = GetRepositoryByID(ctx, c.RepoID)
return err
}
// LoadAttachments loads attachments
func (c *CommitComment) LoadAttachments(ctx context.Context) error {
if len(c.Attachments) > 0 {
return nil
}
var err error
c.Attachments, err = GetAttachmentsByCommentID(ctx, c.ID)
return err
}
// DiffSide returns "previous" if Line is negative and "proposed" if positive
func (c *CommitComment) DiffSide() string {
if c.Line < 0 {
return "previous"
}
return "proposed"
}
// UnsignedLine returns the absolute value of the line number
func (c *CommitComment) UnsignedLine() uint64 {
if c.Line < 0 {
return uint64(c.Line * -1)
}
return uint64(c.Line)
}
// HashTag returns unique hash tag for comment
func (c *CommitComment) HashTag() string {
return fmt.Sprintf("commitcomment-%d", c.ID)
}
// UpdateCommitComment updates a commit comment
func UpdateCommitComment(ctx context.Context, comment *CommitComment) error {
_, err := db.GetEngine(ctx).ID(comment.ID).AllCols().Update(comment)
return err
}
// DeleteCommitComment deletes a commit comment
func DeleteCommitComment(ctx context.Context, comment *CommitComment) error {
_, err := db.GetEngine(ctx).ID(comment.ID).Delete(comment)
return err
}