mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-03 12:41:10 +02:00
Merge 926b3aec4eb434760913c6319ececdc58da4f69a into 4fa319b9dca46d3d553d4d4e8f74ca0e009693c6
This commit is contained in:
commit
ef4f2a1ff9
@ -115,6 +115,53 @@ func init() {
|
||||
db.RegisterModel(new(Notification))
|
||||
}
|
||||
|
||||
// CreateCommitCommentNotification creates notifications for a commit comment.
|
||||
// It notifies the commit author (if they're a Gitea user) and any @mentioned users.
|
||||
func CreateCommitCommentNotification(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, comment *issues_model.Comment, commitAuthorEmail string, mentionedUsernames []string) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
receiverIDs := make(map[int64]struct{})
|
||||
|
||||
// Notify the commit author if they map to a Gitea user
|
||||
if commitAuthorEmail != "" {
|
||||
author, err := user_model.GetUserByEmail(ctx, commitAuthorEmail)
|
||||
if err == nil && author.ID != doer.ID {
|
||||
receiverIDs[author.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify @mentioned users
|
||||
if len(mentionedUsernames) > 0 {
|
||||
mentionedIDs, err := user_model.GetUserIDsByNames(ctx, mentionedUsernames, true)
|
||||
if err == nil {
|
||||
for _, id := range mentionedIDs {
|
||||
if id != doer.ID {
|
||||
receiverIDs[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(receiverIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var notifications []*Notification
|
||||
for uid := range receiverIDs {
|
||||
notifications = append(notifications, &Notification{
|
||||
UserID: uid,
|
||||
RepoID: repo.ID,
|
||||
Status: NotificationStatusUnread,
|
||||
Source: NotificationSourceCommit,
|
||||
CommitID: comment.CommitSHA,
|
||||
CommentID: comment.ID,
|
||||
UpdatedBy: doer.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return db.Insert(ctx, notifications)
|
||||
})
|
||||
}
|
||||
|
||||
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
|
||||
func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
|
||||
@ -116,6 +116,8 @@ const (
|
||||
CommentTypeUnpin // 37 unpin Issue/PullRequest
|
||||
|
||||
CommentTypeChangeTimeEstimate // 38 Change time estimate
|
||||
|
||||
CommentTypeCommitComment // 39 Inline comment on a commit diff (not part of a PR review)
|
||||
)
|
||||
|
||||
var commentStrings = []string{
|
||||
@ -158,6 +160,7 @@ var commentStrings = []string{
|
||||
"pin",
|
||||
"unpin",
|
||||
"change_time_estimate",
|
||||
"commit_comment",
|
||||
}
|
||||
|
||||
func (t CommentType) String() string {
|
||||
@ -175,7 +178,7 @@ func AsCommentType(typeName string) CommentType {
|
||||
|
||||
func (t CommentType) HasContentSupport() bool {
|
||||
switch t {
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview:
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeDismissReview, CommentTypeCommitComment:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -183,7 +186,7 @@ func (t CommentType) HasContentSupport() bool {
|
||||
|
||||
func (t CommentType) HasAttachmentSupport() bool {
|
||||
switch t {
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview:
|
||||
case CommentTypeComment, CommentTypeCode, CommentTypeReview, CommentTypeCommitComment:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -257,6 +260,7 @@ type Comment struct {
|
||||
OriginalAuthorID int64
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Issue *Issue `xorm:"-"`
|
||||
RepoID int64 `xorm:"INDEX DEFAULT 0"` // used by commit comments (type=39) which don't have an issue
|
||||
LabelID int64
|
||||
Label *Label `xorm:"-"`
|
||||
AddedLabels []*Label `xorm:"-"`
|
||||
@ -348,6 +352,9 @@ func (c *Comment) LoadIssue(ctx context.Context) (err error) {
|
||||
if c.Issue != nil {
|
||||
return nil
|
||||
}
|
||||
if c.IssueID == 0 {
|
||||
return nil
|
||||
}
|
||||
c.Issue, err = GetIssueByID(ctx, c.IssueID)
|
||||
return err
|
||||
}
|
||||
|
||||
95
models/issues/commit_comment.go
Normal file
95
models/issues/commit_comment.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
)
|
||||
|
||||
// FileCommitComments holds commit comments for a single file,
|
||||
// split by side (left = old, right = new) with int keys matching DiffLine indices.
|
||||
type FileCommitComments struct {
|
||||
Left map[int][]*Comment
|
||||
Right map[int][]*Comment
|
||||
}
|
||||
|
||||
// CommitCommentsForDiff maps file paths to their commit comments.
|
||||
type CommitCommentsForDiff map[string]*FileCommitComments
|
||||
|
||||
// FindCommitCommentsByCommitSHA returns all comments for a given commit in a repo.
|
||||
func FindCommitCommentsByCommitSHA(ctx context.Context, repoID int64, commitSHA string) ([]*Comment, error) {
|
||||
comments := make([]*Comment, 0)
|
||||
if err := db.GetEngine(ctx).
|
||||
Where("repo_id = ? AND commit_sha = ? AND type = ?", repoID, commitSHA, CommentTypeCommitComment).
|
||||
OrderBy("created_unix ASC").
|
||||
Find(&comments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := CommentList(comments).LoadPosters(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
// FindCommitCommentsForDiff returns comments grouped by path and side for rendering in a diff view.
|
||||
func FindCommitCommentsForDiff(ctx context.Context, repoID int64, commitSHA string) (CommitCommentsForDiff, error) {
|
||||
comments, err := FindCommitCommentsByCommitSHA(ctx, repoID, commitSHA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(CommitCommentsForDiff)
|
||||
for _, c := range comments {
|
||||
fcc, ok := result[c.TreePath]
|
||||
if !ok {
|
||||
fcc = &FileCommitComments{
|
||||
Left: make(map[int][]*Comment),
|
||||
Right: make(map[int][]*Comment),
|
||||
}
|
||||
result[c.TreePath] = fcc
|
||||
}
|
||||
if c.Line < 0 {
|
||||
idx := int(-c.Line)
|
||||
fcc.Left[idx] = append(fcc.Left[idx], c)
|
||||
} else {
|
||||
idx := int(c.Line)
|
||||
fcc.Right[idx] = append(fcc.Right[idx], c)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateCommitComment creates a Comment with type CommitComment.
|
||||
func CreateCommitComment(ctx context.Context, comment *Comment) error {
|
||||
comment.Type = CommentTypeCommitComment
|
||||
_, err := db.GetEngine(ctx).Insert(comment)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCommitComment deletes a commit comment by ID, verifying it belongs to the given repo.
|
||||
func DeleteCommitComment(ctx context.Context, repoID, commentID int64) error {
|
||||
_, err := db.GetEngine(ctx).
|
||||
Where("id = ? AND repo_id = ? AND type = ?", commentID, repoID, CommentTypeCommitComment).
|
||||
Delete(&Comment{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCommitCommentByID returns a commit comment by ID, verifying it belongs to the given repo.
|
||||
func GetCommitCommentByID(ctx context.Context, repoID, commentID int64) (*Comment, error) {
|
||||
c := &Comment{}
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("id = ? AND repo_id = ? AND type = ?", commentID, repoID, CommentTypeCommitComment).
|
||||
Get(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, db.ErrNotExist{Resource: "CommitComment", ID: commentID}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
167
models/issues/commit_comment_test.go
Normal file
167
models/issues/commit_comment_test.go
Normal file
@ -0,0 +1,167 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issues_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateCommitComment(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: "abc123def456",
|
||||
TreePath: "README.md",
|
||||
Line: 10,
|
||||
Content: "looks good",
|
||||
}
|
||||
|
||||
err := issues_model.CreateCommitComment(t.Context(), comment)
|
||||
require.NoError(t, err)
|
||||
assert.Positive(t, comment.ID)
|
||||
assert.Equal(t, issues_model.CommentTypeCommitComment, comment.Type)
|
||||
|
||||
// Verify it's in the DB
|
||||
loaded, err := issues_model.GetCommitCommentByID(t.Context(), 1, comment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "README.md", loaded.TreePath)
|
||||
assert.Equal(t, int64(10), loaded.Line)
|
||||
assert.Equal(t, "looks good", loaded.Content)
|
||||
assert.Equal(t, int64(1), loaded.RepoID)
|
||||
}
|
||||
|
||||
func TestGetCommitCommentByID_WrongRepo(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: "abc123def456",
|
||||
TreePath: "main.go",
|
||||
Line: 5,
|
||||
Content: "test",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), comment))
|
||||
|
||||
// Trying to load with wrong repo ID should fail
|
||||
_, err := issues_model.GetCommitCommentByID(t.Context(), 999, comment.ID)
|
||||
assert.True(t, db.IsErrNotExist(err))
|
||||
}
|
||||
|
||||
func TestDeleteCommitComment(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: "abc123def456",
|
||||
TreePath: "README.md",
|
||||
Line: 10,
|
||||
Content: "to be deleted",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), comment))
|
||||
|
||||
err := issues_model.DeleteCommitComment(t.Context(), 1, comment.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify it's gone
|
||||
_, err = issues_model.GetCommitCommentByID(t.Context(), 1, comment.ID)
|
||||
assert.True(t, db.IsErrNotExist(err))
|
||||
}
|
||||
|
||||
func TestDeleteCommitComment_WrongRepo(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: "abc123def456",
|
||||
TreePath: "README.md",
|
||||
Line: 10,
|
||||
Content: "should not be deleted",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), comment))
|
||||
|
||||
// Delete with wrong repo should not actually delete
|
||||
err := issues_model.DeleteCommitComment(t.Context(), 999, comment.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify it's still there
|
||||
loaded, err := issues_model.GetCommitCommentByID(t.Context(), 1, comment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, comment.ID, loaded.ID)
|
||||
}
|
||||
|
||||
func TestFindCommitCommentsByCommitSHA(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
sha := "deadbeef1234"
|
||||
// Create two comments on same commit, different lines
|
||||
for _, line := range []int64{5, -10} {
|
||||
c := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: sha,
|
||||
TreePath: "main.go",
|
||||
Line: line,
|
||||
Content: "comment",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), c))
|
||||
}
|
||||
|
||||
comments, err := issues_model.FindCommitCommentsByCommitSHA(t.Context(), 1, sha)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, comments, 2)
|
||||
|
||||
// Different repo should return empty
|
||||
comments, err = issues_model.FindCommitCommentsByCommitSHA(t.Context(), 999, sha)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, comments)
|
||||
}
|
||||
|
||||
func TestFindCommitCommentsForDiff(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
sha := "cafebabe5678"
|
||||
// Left side comment (negative line = old/previous side)
|
||||
c1 := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: sha,
|
||||
TreePath: "file.go",
|
||||
Line: -3,
|
||||
Content: "old side",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), c1))
|
||||
|
||||
// Right side comment (positive line = new/proposed side)
|
||||
c2 := &issues_model.Comment{
|
||||
PosterID: 2,
|
||||
RepoID: 1,
|
||||
CommitSHA: sha,
|
||||
TreePath: "file.go",
|
||||
Line: 7,
|
||||
Content: "new side",
|
||||
}
|
||||
require.NoError(t, issues_model.CreateCommitComment(t.Context(), c2))
|
||||
|
||||
result, err := issues_model.FindCommitCommentsForDiff(t.Context(), 1, sha)
|
||||
require.NoError(t, err)
|
||||
|
||||
fcc, ok := result["file.go"]
|
||||
require.True(t, ok)
|
||||
assert.Len(t, fcc.Left[3], 1)
|
||||
assert.Equal(t, "old side", fcc.Left[3][0].Content)
|
||||
assert.Len(t, fcc.Right[7], 1)
|
||||
assert.Equal(t, "new side", fcc.Right[7][0].Content)
|
||||
}
|
||||
@ -405,6 +405,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(328, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob),
|
||||
newMigration(329, "Add unique constraint for user badge", v1_26.AddUniqueIndexForUserBadge),
|
||||
newMigration(330, "Add name column to webhook", v1_26.AddNameToWebhook),
|
||||
newMigration(331, "Add repo_id column to comment table for commit comments", v1_26.AddCommitCommentColumns),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
21
models/migrations/v1_26/v331.go
Normal file
21
models/migrations/v1_26/v331.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_26
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddCommitCommentColumns(x *xorm.Engine) error {
|
||||
// Add RepoID column to the comment table for commit comments.
|
||||
// Commit comments (type=39) store repo_id directly on the comment
|
||||
// instead of deriving it through issue_id, since they don't belong
|
||||
// to any issue. Combined with the existing commit_sha column, this
|
||||
// allows querying commit comments without a junction table.
|
||||
type Comment struct {
|
||||
RepoID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Comment))
|
||||
}
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -424,6 +425,30 @@ func Diff(ctx *context.Context) {
|
||||
ctx.Data["MergedPRIssueNumber"] = pr.Index
|
||||
}
|
||||
|
||||
// Load inline commit comments for the diff view
|
||||
commitComments, err := issues_model.FindCommitCommentsForDiff(ctx, ctx.Repo.Repository.ID, commitID)
|
||||
if err != nil {
|
||||
log.Error("FindCommitCommentsForDiff: %v", err)
|
||||
}
|
||||
// Render markdown content for each commit comment
|
||||
renderCommitComments := func(commentsByLine map[int][]*issues_model.Comment) {
|
||||
for _, comments := range commentsByLine {
|
||||
for _, c := range comments {
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{})
|
||||
c.RenderedContent, err = markdown.RenderString(rctx, c.Content)
|
||||
if err != nil {
|
||||
log.Error("RenderString for commit comment %d: %v", c.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, fcc := range commitComments {
|
||||
renderCommitComments(fcc.Left)
|
||||
renderCommitComments(fcc.Right)
|
||||
}
|
||||
ctx.Data["CommitComments"] = commitComments
|
||||
ctx.Data["CanComment"] = ctx.Doer != nil && ctx.Repo.CanWrite(unit_model.TypeCode)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCommitPage)
|
||||
}
|
||||
|
||||
|
||||
159
routers/web/repo/commit_comment.go
Normal file
159
routers/web/repo/commit_comment.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
)
|
||||
|
||||
var (
|
||||
tplNewCommitComment templates.TplName = "repo/diff/new_commit_comment"
|
||||
tplCommitConversation templates.TplName = "repo/diff/commit_conversation"
|
||||
)
|
||||
|
||||
// RenderNewCommitCommentForm renders the comment form for inline commit comments.
|
||||
func RenderNewCommitCommentForm(ctx *context.Context) {
|
||||
commitSHA := ctx.PathParam("sha")
|
||||
ctx.Data["CommitID"] = commitSHA
|
||||
ctx.Data["PageIsDiff"] = true
|
||||
ctx.HTML(http.StatusOK, tplNewCommitComment)
|
||||
}
|
||||
|
||||
// CreateCommitComment handles creating an inline comment on a commit diff.
|
||||
func CreateCommitComment(ctx *context.Context) {
|
||||
commitSHA := ctx.PathParam("sha")
|
||||
if commitSHA == "" {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
content := ctx.FormString("content")
|
||||
treePath := ctx.FormString("tree_path")
|
||||
side := ctx.FormString("side")
|
||||
line := ctx.FormInt64("line")
|
||||
|
||||
if content == "" || treePath == "" || line == 0 {
|
||||
ctx.JSONError("content, tree_path, and line are required")
|
||||
return
|
||||
}
|
||||
|
||||
if side == "previous" {
|
||||
line = -line
|
||||
}
|
||||
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(commitSHA)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.ServerError("GetCommit", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
fullSHA := commit.ID.String()
|
||||
|
||||
// Generate diff context patch around the commented line
|
||||
var patch string
|
||||
var parentSHA string
|
||||
if commit.ParentCount() > 0 {
|
||||
parentID, err := commit.ParentID(0)
|
||||
if err == nil {
|
||||
parentSHA = parentID.String()
|
||||
}
|
||||
}
|
||||
if parentSHA != "" {
|
||||
absLine := line
|
||||
isOld := line < 0
|
||||
if isOld {
|
||||
absLine = -line
|
||||
}
|
||||
patch, err = git.GetFileDiffCutAroundLine(
|
||||
ctx.Repo.GitRepo, parentSHA, fullSHA, treePath,
|
||||
absLine, isOld, setting.UI.CodeCommentLines,
|
||||
)
|
||||
if err != nil {
|
||||
log.Debug("GetFileDiffCutAroundLine failed for commit comment: %v", err)
|
||||
}
|
||||
if patch == "" {
|
||||
patch, err = gitdiff.GeneratePatchForUnchangedLine(ctx.Repo.GitRepo, fullSHA, treePath, line, setting.UI.CodeCommentLines)
|
||||
if err != nil {
|
||||
log.Debug("GeneratePatchForUnchangedLine failed for commit comment: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comment := &issues_model.Comment{
|
||||
PosterID: ctx.Doer.ID,
|
||||
Poster: ctx.Doer,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
CommitSHA: fullSHA,
|
||||
TreePath: treePath,
|
||||
Line: line,
|
||||
Content: content,
|
||||
Patch: patch,
|
||||
}
|
||||
|
||||
if err := issues_model.CreateCommitComment(ctx, comment); err != nil {
|
||||
ctx.ServerError("CreateCommitComment", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send notifications to commit author and @mentioned users
|
||||
mentions := references.FindAllMentionsMarkdown(content)
|
||||
if err := activities_model.CreateCommitCommentNotification(ctx, ctx.Doer, ctx.Repo.Repository, comment, commit.Author.Email, mentions); err != nil {
|
||||
log.Error("CreateCommitCommentNotification: %v", err)
|
||||
}
|
||||
|
||||
// Render markdown content
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{})
|
||||
comment.RenderedContent, err = markdown.RenderString(rctx, comment.Content)
|
||||
if err != nil {
|
||||
log.Error("RenderString for commit comment %d: %v", comment.ID, err)
|
||||
}
|
||||
|
||||
ctx.Data["CommitID"] = fullSHA
|
||||
ctx.Data["comments"] = []*issues_model.Comment{comment}
|
||||
ctx.HTML(http.StatusOK, tplCommitConversation)
|
||||
}
|
||||
|
||||
// DeleteCommitComment handles deleting an inline comment on a commit.
|
||||
func DeleteCommitComment(ctx *context.Context) {
|
||||
commentID := ctx.PathParamInt64("id")
|
||||
if commentID <= 0 {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
comment, err := issues_model.GetCommitCommentByID(ctx, ctx.Repo.Repository.ID, commentID)
|
||||
if err != nil {
|
||||
ctx.NotFound(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow deletion by the comment author or anyone with write access to code
|
||||
if comment.PosterID != ctx.Doer.ID && !ctx.Repo.CanWrite(unit_model.TypeCode) {
|
||||
ctx.HTTPError(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := issues_model.DeleteCommitComment(ctx, ctx.Repo.Repository.ID, commentID); err != nil {
|
||||
ctx.ServerError("DeleteCommitComment", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{"ok": true})
|
||||
}
|
||||
@ -1685,6 +1685,9 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("/graph", repo.Graph)
|
||||
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)
|
||||
m.Get("/commit/{sha:([a-f0-9]{7,64})$}/comment", reqSignIn, reqUnitCodeReader, repo.RenderNewCommitCommentForm)
|
||||
m.Post("/commit/{sha:([a-f0-9]{7,64})$}/comment", reqSignIn, reqUnitCodeReader, context.RepoMustNotBeArchived(), repo.CreateCommitComment)
|
||||
m.Post("/commit/{sha:([a-f0-9]{7,64})$}/comment/{id}/delete", reqSignIn, reqUnitCodeReader, context.RepoMustNotBeArchived(), repo.DeleteCommitComment)
|
||||
|
||||
// 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)
|
||||
|
||||
@ -184,7 +184,7 @@
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<table class="chroma" data-new-comment-url="{{$.Issue.Link}}/files/reviews/new_comment" data-path="{{$file.Name}}">
|
||||
<table class="chroma" data-new-comment-url="{{if $.PageIsDiff}}{{$.RepoLink}}/commit/{{$.CommitID}}/comment{{else}}{{$.Issue.Link}}/files/reviews/new_comment{{end}}" data-path="{{$file.Name}}">
|
||||
{{if $.IsSplitStyle}}
|
||||
{{template "repo/diff/section_split" dict "file" . "root" $}}
|
||||
{{else}}
|
||||
|
||||
25
templates/repo/diff/commit_comment_form.tmpl
Normal file
25
templates/repo/diff/commit_comment_form.tmpl
Normal file
@ -0,0 +1,25 @@
|
||||
{{if and ctx.RootData.SignedUserID (not ctx.RootData.Repository.IsArchived)}}
|
||||
<form class="ui form {{if $.hidden}}tw-hidden comment-form{{end}}" action="{{$.root.RepoLink}}/commit/{{$.root.CommitID}}/comment" method="post">
|
||||
<input type="hidden" name="side" value="">
|
||||
<input type="hidden" name="line" value="">
|
||||
<input type="hidden" name="tree_path" value="">
|
||||
<div class="field">
|
||||
{{template "shared/combomarkdowneditor" (dict
|
||||
"CustomInit" true
|
||||
"MarkdownEditorContext" (ctx.MiscUtils.MarkdownEditorComment ctx.RootData.Repository)
|
||||
"TextareaName" "content"
|
||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.comment.placeholder")
|
||||
"DropzoneParentContainer" "form"
|
||||
"DisableAutosize" "true"
|
||||
)}}
|
||||
</div>
|
||||
<div class="field footer">
|
||||
<div class="flex-text-block tw-justify-end">
|
||||
<button type="submit" class="ui submit primary tiny button btn-add-single">{{ctx.Locale.Tr "repo.diff.comment.add_single_comment"}}</button>
|
||||
{{if or (not $.HasComments) $.hidden}}
|
||||
<button type="button" class="ui submit tiny basic button btn-cancel cancel-code-comment">{{ctx.Locale.Tr "cancel"}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
35
templates/repo/diff/commit_comments.tmpl
Normal file
35
templates/repo/diff/commit_comments.tmpl
Normal file
@ -0,0 +1,35 @@
|
||||
{{range .comments}}
|
||||
|
||||
{{$createdStr := DateUtils.TimeSince .CreatedUnix}}
|
||||
<div class="comment" id="{{.HashTag}}">
|
||||
<div class="tw-mt-2 tw-mr-4">
|
||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||
</div>
|
||||
<div class="content comment-container">
|
||||
<div class="comment-header avatar-content-left-arrow">
|
||||
<div class="comment-header-left">
|
||||
<span class="tw-text-text-light muted-links">
|
||||
{{template "shared/user/namelink" .Poster}}
|
||||
{{ctx.Locale.Tr "repo.issues.commented_at" .HashTag $createdStr}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="comment-header-right">
|
||||
{{if and $.root.IsSigned (or (eq $.root.SignedUserID .PosterID) $.root.CanWriteCode)}}
|
||||
<div class="item context js-aria-clickable delete-comment" data-comment-id="{{.HashTag}}" data-url="{{$.root.RepoLink}}/commit/{{.CommitSHA}}/comment/{{.ID}}/delete" data-locale="{{ctx.Locale.Tr "repo.issues.delete_comment_confirm"}}">
|
||||
{{svg "octicon-trash" 14}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui attached segment comment-body">
|
||||
<div class="render-content markup">
|
||||
{{if .RenderedContent}}
|
||||
{{.RenderedContent}}
|
||||
{{else}}
|
||||
<span class="no-content">{{ctx.Locale.Tr "repo.issues.no_content"}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
25
templates/repo/diff/commit_conversation.tmpl
Normal file
25
templates/repo/diff/commit_conversation.tmpl
Normal file
@ -0,0 +1,25 @@
|
||||
{{if .comments}}
|
||||
{{$comment := index .comments 0}}
|
||||
<div class="conversation-holder" data-path="{{$comment.TreePath}}" data-side="{{if eq $comment.DiffSide "previous"}}left{{else}}right{{end}}" data-idx="{{$comment.UnsignedLine}}">
|
||||
<div id="code-comments-{{$comment.ID}}" class="field comment-code-cloud">
|
||||
<div class="comment-list">
|
||||
<div class="ui comments">
|
||||
{{template "repo/diff/commit_comments" dict "root" $.root "comments" .comments}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-text-block tw-mt-2 tw-flex-wrap tw-justify-end">
|
||||
<div class="ui buttons">
|
||||
<button class="ui icon tiny basic button previous-conversation">
|
||||
{{svg "octicon-arrow-up" 12}} {{ctx.Locale.Tr "repo.issues.previous"}}
|
||||
</button>
|
||||
<button class="ui icon tiny basic button next-conversation">
|
||||
{{svg "octicon-arrow-down" 12}} {{ctx.Locale.Tr "repo.issues.next"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{if and $.root.SignedUserID (not $.root.Repository.IsArchived)}}
|
||||
{{template "repo/diff/commit_comment_form" dict "hidden" true "root" $.root}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
5
templates/repo/diff/new_commit_comment.tmpl
Normal file
5
templates/repo/diff/new_commit_comment.tmpl
Normal file
@ -0,0 +1,5 @@
|
||||
<div class="conversation-holder">
|
||||
<div class="field comment-code-cloud">
|
||||
{{template "repo/diff/commit_comment_form" dict "root" $}}
|
||||
</div>
|
||||
</div>
|
||||
@ -1,5 +1,8 @@
|
||||
{{$file := .file}}
|
||||
{{$diffBlobExcerptData := $.root.DiffBlobExcerptData}}
|
||||
{{$commitComments := $.root.CommitComments}}
|
||||
{{$ccFile := ""}}
|
||||
{{if $commitComments}}{{$ccFile = index $commitComments $file.Name}}{{end}}
|
||||
<colgroup>
|
||||
<col width="50">
|
||||
<col width="10">
|
||||
@ -28,7 +31,7 @@
|
||||
<td class="lines-escape del-code lines-escape-old">{{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $leftDiff}}"></button>{{end}}{{end}}</td>
|
||||
<td class="lines-type-marker lines-type-marker-old del-code"><span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span></td>
|
||||
<td class="lines-code lines-code-old del-code">
|
||||
{{- if and $.root.SignedUserID $.root.PageIsPullFiles -}}
|
||||
{{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}}
|
||||
<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">
|
||||
{{- svg "octicon-plus" -}}
|
||||
</button>
|
||||
@ -43,7 +46,7 @@
|
||||
<td class="lines-escape add-code lines-escape-new">{{if $match.RightIdx}}{{if $rightDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $rightDiff}}"></button>{{end}}{{end}}</td>
|
||||
<td class="lines-type-marker lines-type-marker-new add-code">{{if $match.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$match.GetLineTypeMarker}}"></span>{{end}}</td>
|
||||
<td class="lines-code lines-code-new add-code">
|
||||
{{- if and $.root.SignedUserID $.root.PageIsPullFiles -}}
|
||||
{{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}}
|
||||
<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $match.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$match.RightIdx}}">
|
||||
{{- svg "octicon-plus" -}}
|
||||
</button>
|
||||
@ -60,7 +63,7 @@
|
||||
<td class="lines-escape lines-escape-old">{{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
|
||||
<td class="lines-type-marker lines-type-marker-old">{{if $line.LeftIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
|
||||
<td class="lines-code lines-code-old">
|
||||
{{- if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2)) -}}
|
||||
{{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) (not (eq .GetType 2)) -}}
|
||||
<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-left{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="left" data-idx="{{$line.LeftIdx}}">
|
||||
{{- svg "octicon-plus" -}}
|
||||
</button>
|
||||
@ -75,7 +78,7 @@
|
||||
<td class="lines-escape lines-escape-new">{{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{template "repo/diff/escape_title" dict "diff" $inlineDiff}}"></button>{{end}}{{end}}</td>
|
||||
<td class="lines-type-marker lines-type-marker-new">{{if $line.RightIdx}}<span class="tw-font-mono" data-type-marker="{{$line.GetLineTypeMarker}}"></span>{{end}}</td>
|
||||
<td class="lines-code lines-code-new">
|
||||
{{- if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3)) -}}
|
||||
{{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) (not (eq .GetType 3)) -}}
|
||||
<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-right{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="right" data-idx="{{$line.RightIdx}}">
|
||||
{{- svg "octicon-plus" -}}
|
||||
</button>
|
||||
@ -132,6 +135,32 @@
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{/* Render commit comments (not PR review comments) */}}
|
||||
{{if $ccFile}}
|
||||
{{$leftCC := ""}}
|
||||
{{if $line.LeftIdx}}{{$leftCC = index $ccFile.Left $line.LeftIdx}}{{end}}
|
||||
{{$rightCC := ""}}
|
||||
{{if and (eq .GetType 3) $hasmatch}}
|
||||
{{$match := index $section.Lines $line.Match}}
|
||||
{{if $match.RightIdx}}{{$rightCC = index $ccFile.Right $match.RightIdx}}{{end}}
|
||||
{{else}}
|
||||
{{if $line.RightIdx}}{{$rightCC = index $ccFile.Right $line.RightIdx}}{{end}}
|
||||
{{end}}
|
||||
{{if or $leftCC $rightCC}}
|
||||
<tr class="add-comment" data-line-type="{{.GetHTMLDiffLineType}}">
|
||||
<td class="add-comment-left" colspan="4">
|
||||
{{if $leftCC}}
|
||||
{{template "repo/diff/commit_conversation" dict "root" $.root "comments" $leftCC}}
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="add-comment-right" colspan="4">
|
||||
{{if $rightCC}}
|
||||
{{template "repo/diff/commit_conversation" dict "root" $.root "comments" $rightCC}}
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
{{$file := .file}}
|
||||
{{/* this tmpl is also used by the PR Conversation page, so "DiffBlobExcerptData" may not exist */}}
|
||||
{{$diffBlobExcerptData := $.root.DiffBlobExcerptData}}
|
||||
{{$commitComments := $.root.CommitComments}}
|
||||
{{$ccFile := ""}}
|
||||
{{if $commitComments}}{{$ccFile = index $commitComments $file.Name}}{{end}}
|
||||
<colgroup>
|
||||
<col width="50">
|
||||
<col width="50">
|
||||
@ -33,7 +36,7 @@
|
||||
<td class="chroma lines-code blob-hunk">{{template "repo/diff/section_code" dict "diff" $inlineDiff}}</td>
|
||||
{{else}}
|
||||
<td class="chroma lines-code{{if (not $line.RightIdx)}} lines-code-old{{end}}">
|
||||
{{- if and $.root.SignedUserID $.root.PageIsPullFiles -}}
|
||||
{{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}}
|
||||
<button type="button" aria-label="{{ctx.Locale.Tr "repo.diff.comment.add_line_comment"}}" class="ui primary button add-code-comment add-code-comment-{{if $line.RightIdx}}right{{else}}left{{end}}{{if (not $line.CanComment)}} tw-invisible{{end}}" data-side="{{if $line.RightIdx}}right{{else}}left{{end}}" data-idx="{{if $line.RightIdx}}{{$line.RightIdx}}{{else}}{{$line.LeftIdx}}{{end}}">
|
||||
{{- svg "octicon-plus" -}}
|
||||
</button>
|
||||
@ -49,5 +52,24 @@
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{/* Render commit comments (not PR review comments) */}}
|
||||
{{if $ccFile}}
|
||||
{{$rightCC := ""}}
|
||||
{{if $line.RightIdx}}{{$rightCC = index $ccFile.Right $line.RightIdx}}{{end}}
|
||||
{{$leftCC := ""}}
|
||||
{{if and $line.LeftIdx (not $line.RightIdx)}}{{$leftCC = index $ccFile.Left $line.LeftIdx}}{{end}}
|
||||
{{if or $rightCC $leftCC}}
|
||||
<tr class="add-comment" data-line-type="{{.GetHTMLDiffLineType}}">
|
||||
<td class="add-comment-left add-comment-right" colspan="5">
|
||||
{{if $rightCC}}
|
||||
{{template "repo/diff/commit_conversation" dict "root" $.root "comments" $rightCC}}
|
||||
{{end}}
|
||||
{{if $leftCC}}
|
||||
{{template "repo/diff/commit_conversation" dict "root" $.root "comments" $leftCC}}
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user