Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions models/issues/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ const (
CommentTypeUnpin // 37 unpin Issue/PullRequest

CommentTypeChangeTimeEstimate // 38 Change time estimate

CommentTypeCommitCode // 39 Comment a line of code in a commit (not part of a pull request)
)

var commentStrings = []string{
Expand Down Expand Up @@ -157,6 +159,7 @@ var commentStrings = []string{
"pin",
"unpin",
"change_time_estimate",
"commit_code",
}

func (t CommentType) String() string {
Expand All @@ -174,23 +177,23 @@ func AsCommentType(typeName string) CommentType {

func (t CommentType) HasContentSupport() bool {
switch t {
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview:
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview, CommentTypeCommitCode:
return true
}
return false
}

func (t CommentType) HasAttachmentSupport() bool {
switch t {
case CommentTypeComment, CommentTypeCode, CommentTypeReview:
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeCommitCode:
return true
}
return false
}

func (t CommentType) HasMailReplySupport() bool {
switch t {
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview, CommentTypeReopen, CommentTypeClose, CommentTypeMergePull, CommentTypeAssignees:
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview, CommentTypeReopen, CommentTypeClose, CommentTypeMergePull, CommentTypeAssignees, CommentTypeCommitCode:
return true
}
return false
Expand Down Expand Up @@ -447,6 +450,9 @@ func (c *Comment) hashLink(ctx context.Context) string {
return "/files#" + c.HashTag()
}
}
if c.Type == CommentTypeCommitCode {
return "/files#" + c.HashTag()
}
return "#" + c.HashTag()
}

Expand Down Expand Up @@ -657,9 +663,9 @@ func (c *Comment) LoadAssigneeUserAndTeam(ctx context.Context) error {
return nil
}

// LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer
// LoadResolveDoer if comment.Type is CommentTypeCode or CommentTypeCommitCode and ResolveDoerID not zero, then load resolveDoer
func (c *Comment) LoadResolveDoer(ctx context.Context) (err error) {
if c.ResolveDoerID == 0 || c.Type != CommentTypeCode {
if c.ResolveDoerID == 0 || (c.Type != CommentTypeCode && c.Type != CommentTypeCommitCode) {
return nil
}
c.ResolveDoer, err = user_model.GetUserByID(ctx, c.ResolveDoerID)
Expand All @@ -674,7 +680,7 @@ func (c *Comment) LoadResolveDoer(ctx context.Context) (err error) {

// IsResolved check if an code comment is resolved
func (c *Comment) IsResolved() bool {
return c.ResolveDoerID != 0 && c.Type == CommentTypeCode
return c.ResolveDoerID != 0 && (c.Type == CommentTypeCode || c.Type == CommentTypeCommitCode)
}

// LoadDepIssueDetails loads Dependent Issue Details
Expand Down Expand Up @@ -862,6 +868,12 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err
}
case CommentTypeCommitCode:
if err = UpdateCommentAttachments(ctx, comment, opts.Attachments); err != nil {
return err
}
// Commit comments don't have an associated issue, so just return here
return nil
case CommentTypeReopen, CommentTypeClose:
if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
return err
Expand Down Expand Up @@ -1076,6 +1088,29 @@ func CountComments(ctx context.Context, opts *FindCommentsOptions) (int64, error
return sess.Count(&Comment{})
}

// FindCommitComments finds all code comments for a specific commit
func FindCommitComments(ctx context.Context, repoID int64, commitSHA string) (CommentList, error) {
comments := make([]*Comment, 0, 10)
return comments, db.GetEngine(ctx).
Where("commit_sha = ?", commitSHA).
And("type = ?", CommentTypeCommitCode).
Asc("created_unix").
Asc("id").
Find(&comments)
}

// FindCommitLineComments finds code comments for a specific file and line in a commit
func FindCommitLineComments(ctx context.Context, commitSHA, treePath string) (CommentList, error) {
comments := make([]*Comment, 0, 10)
return comments, db.GetEngine(ctx).
Where("commit_sha = ?", commitSHA).
And("tree_path = ?", treePath).
And("type = ?", CommentTypeCommitCode).
Asc("created_unix").
Asc("id").
Find(&comments)
}

// UpdateCommentInvalidate updates comment invalidated column
func UpdateCommentInvalidate(ctx context.Context, c *Comment) error {
_, err := db.GetEngine(ctx).ID(c.ID).Cols("invalidated").Update(c)
Expand Down
154 changes: 154 additions & 0 deletions routers/web/repo/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/forms"
git_service "code.gitea.io/gitea/services/git"
"code.gitea.io/gitea/services/gitdiff"
repo_service "code.gitea.io/gitea/services/repository"
Expand Down Expand Up @@ -417,6 +420,25 @@ func Diff(ctx *context.Context) {
ctx.Data["MergedPRIssueNumber"] = pr.Index
}

// Load commit comments for inline display
comments, err := issues_model.FindCommitComments(ctx, ctx.Repo.Repository.ID, commitID)
if err != nil {
log.Error("FindCommitComments: %v", err)
} else {
if err := comments.LoadPosters(ctx); err != nil {
log.Error("LoadPosters: %v", err)
}
if err := comments.LoadAttachments(ctx); err != nil {
log.Error("LoadAttachments: %v", err)
}
ctx.Data["CommitComments"] = comments
}

// Mark this as a commit page to enable comment UI
ctx.Data["PageIsCommit"] = true
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")

ctx.HTML(http.StatusOK, tplCommitPage)
}

Expand Down Expand Up @@ -469,3 +491,135 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m
}
return commits, nil
}

// RenderNewCommitCodeCommentForm renders the form for creating a new commit code comment
func RenderNewCommitCodeCommentForm(ctx *context.Context) {
ctx.Data["PageIsCommit"] = true
ctx.Data["AfterCommitID"] = ctx.PathParam("sha")
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
// Use the same template as PR new comments (defined in pull_review.go)
ctx.HTML(http.StatusOK, "repo/diff/new_comment")
}

// CreateCommitCodeComment creates an inline comment on a commit
func CreateCommitCodeComment(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CodeCommentForm)
commitSHA := ctx.PathParam("sha")

if ctx.Written() {
return
}

if ctx.HasError() {
ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
ctx.Redirect(fmt.Sprintf("%s/commit/%s", ctx.Repo.RepoLink, commitSHA))
return
}

// Convert line to signed line (negative for previous side)
signedLine := form.Line
if form.Side == "previous" {
signedLine *= -1
}

var attachments []string
if setting.Attachment.Enabled {
attachments = form.Files
}

// Create the comment using the service layer
comment, err := repo_service.CreateCommitCodeComment(
ctx,
ctx.Doer,
ctx.Repo.Repository,
ctx.Repo.GitRepo,
commitSHA,
signedLine,
form.Content,
form.TreePath,
attachments,
)
if err != nil {
ctx.ServerError("CreateCommitCodeComment", err)
return
}

log.Trace("Commit comment created: %d for commit %s in %-v", comment.ID, commitSHA, ctx.Repo.Repository)

// Render the comment
ctx.Data["Comment"] = comment
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")

ctx.JSON(http.StatusOK, map[string]any{
"ok": true,
"comment": comment,
})
}

// UpdateCommitCodeComment updates an existing commit inline comment
func UpdateCommitCodeComment(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CodeCommentForm)
commentID := ctx.PathParamInt64(":id")

comment, err := issues_model.GetCommentByID(ctx, commentID)
if err != nil {
ctx.ServerError("GetCommentByID", err)
return
}

// Verify this is a commit comment
if comment.Type != issues_model.CommentTypeCommitCode || comment.CommitSHA == "" {
ctx.NotFound(errors.New("not a commit code comment"))
return
}

// Verify the comment belongs to this repository
if comment.PosterID != ctx.Doer.ID {
ctx.HTTPError(http.StatusForbidden)
return
}

var attachments []string
if setting.Attachment.Enabled {
attachments = form.Files
}

// Update the comment
if err := repo_service.UpdateCommitCodeComment(ctx, ctx.Doer, comment, form.Content, attachments); err != nil {
ctx.ServerError("UpdateCommitCodeComment", err)
return
}

ctx.JSON(http.StatusOK, map[string]any{
"ok": true,
})
}

// DeleteCommitCodeComment deletes a commit inline comment
func DeleteCommitCodeComment(ctx *context.Context) {
commentID := ctx.PathParamInt64(":id")

comment, err := issues_model.GetCommentByID(ctx, commentID)
if err != nil {
ctx.ServerError("GetCommentByID", err)
return
}

// Verify this is a commit comment
if comment.Type != issues_model.CommentTypeCommitCode || comment.CommitSHA == "" {
ctx.NotFound(errors.New("not a commit code comment"))
return
}

// Delete the comment
if err := repo_service.DeleteCommitCodeComment(ctx, ctx.Doer, comment); err != nil {
ctx.ServerError("DeleteCommitCodeComment", err)
return
}

ctx.JSON(http.StatusOK, map[string]any{
"ok": true,
})
}
10 changes: 10 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1616,6 +1616,16 @@ func registerWebRoutes(m *web.Router) {
m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:([a-f0-9]{7,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)

// Commit inline code comments
m.Group("/commit/{sha:([a-f0-9]{7,64})$}/comments", func() {
m.Get("/new", reqSignIn, repo.RenderNewCommitCodeCommentForm)
m.Post("", web.Bind(forms.CodeCommentForm{}), reqSignIn, repo.CreateCommitCodeComment)
m.Group("/{id}", func() {
m.Post("", web.Bind(forms.CodeCommentForm{}), reqSignIn, repo.UpdateCommitCodeComment)
m.Delete("", reqSignIn, repo.DeleteCommitCodeComment)
})
})

// FIXME: this route `/cherry-pick/{sha}` doesn't seem useful or right, the new code always uses `/_cherrypick/` which could handle branch name correctly
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, context.RepoRefByDefaultBranch(), repo.CherryPick)
}, repo.MustBeNotEmpty)
Expand Down
Loading