diff --git a/models/activities/notification.go b/models/activities/notification.go index 8a830c5aa2..5b4578edfb 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -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 { diff --git a/models/issues/comment.go b/models/issues/comment.go index 25e74c01ea..eae61d803a 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -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 } diff --git a/models/issues/commit_comment.go b/models/issues/commit_comment.go new file mode 100644 index 0000000000..4356741fd4 --- /dev/null +++ b/models/issues/commit_comment.go @@ -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 +} diff --git a/models/issues/commit_comment_test.go b/models/issues/commit_comment_test.go new file mode 100644 index 0000000000..18c9fab68f --- /dev/null +++ b/models/issues/commit_comment_test.go @@ -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) +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index db74ff78d5..a9ee6eacda 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -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 } diff --git a/models/migrations/v1_26/v331.go b/models/migrations/v1_26/v331.go new file mode 100644 index 0000000000..5fdd62758f --- /dev/null +++ b/models/migrations/v1_26/v331.go @@ -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)) +} diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 168d959494..d33c6da5bc 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -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) } diff --git a/routers/web/repo/commit_comment.go b/routers/web/repo/commit_comment.go new file mode 100644 index 0000000000..c33a073d51 --- /dev/null +++ b/routers/web/repo/commit_comment.go @@ -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}) +} diff --git a/routers/web/web.go b/routers/web/web.go index e3dcf27cc4..3463250ad5 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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) diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 390e41ec34..b81df83c73 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -184,7 +184,7 @@ {{end}} {{else}} - +
{{if $.IsSplitStyle}} {{template "repo/diff/section_split" dict "file" . "root" $}} {{else}} diff --git a/templates/repo/diff/commit_comment_form.tmpl b/templates/repo/diff/commit_comment_form.tmpl new file mode 100644 index 0000000000..88fa85827c --- /dev/null +++ b/templates/repo/diff/commit_comment_form.tmpl @@ -0,0 +1,25 @@ +{{if and ctx.RootData.SignedUserID (not ctx.RootData.Repository.IsArchived)}} + + + + +
+ {{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" + )}} +
+ + +{{end}} diff --git a/templates/repo/diff/commit_comments.tmpl b/templates/repo/diff/commit_comments.tmpl new file mode 100644 index 0000000000..49763cd442 --- /dev/null +++ b/templates/repo/diff/commit_comments.tmpl @@ -0,0 +1,35 @@ +{{range .comments}} + +{{$createdStr := DateUtils.TimeSince .CreatedUnix}} +
+
+ {{template "shared/user/avatarlink" dict "user" .Poster}} +
+
+
+
+ + {{template "shared/user/namelink" .Poster}} + {{ctx.Locale.Tr "repo.issues.commented_at" .HashTag $createdStr}} + +
+
+ {{if and $.root.IsSigned (or (eq $.root.SignedUserID .PosterID) $.root.CanWriteCode)}} +
+ {{svg "octicon-trash" 14}} +
+ {{end}} +
+
+
+
+ {{if .RenderedContent}} + {{.RenderedContent}} + {{else}} + {{ctx.Locale.Tr "repo.issues.no_content"}} + {{end}} +
+
+
+
+{{end}} diff --git a/templates/repo/diff/commit_conversation.tmpl b/templates/repo/diff/commit_conversation.tmpl new file mode 100644 index 0000000000..3ace4c70ea --- /dev/null +++ b/templates/repo/diff/commit_conversation.tmpl @@ -0,0 +1,25 @@ +{{if .comments}} + {{$comment := index .comments 0}} +
+
+
+
+ {{template "repo/diff/commit_comments" dict "root" $.root "comments" .comments}} +
+
+
+
+ + +
+
+ {{if and $.root.SignedUserID (not $.root.Repository.IsArchived)}} + {{template "repo/diff/commit_comment_form" dict "hidden" true "root" $.root}} + {{end}} +
+
+{{end}} diff --git a/templates/repo/diff/new_commit_comment.tmpl b/templates/repo/diff/new_commit_comment.tmpl new file mode 100644 index 0000000000..fa54107609 --- /dev/null +++ b/templates/repo/diff/new_commit_comment.tmpl @@ -0,0 +1,5 @@ +
+
+ {{template "repo/diff/commit_comment_form" dict "root" $}} +
+
diff --git a/templates/repo/diff/section_split.tmpl b/templates/repo/diff/section_split.tmpl index ab23b1b934..d53a8577e6 100644 --- a/templates/repo/diff/section_split.tmpl +++ b/templates/repo/diff/section_split.tmpl @@ -1,5 +1,8 @@ {{$file := .file}} {{$diffBlobExcerptData := $.root.DiffBlobExcerptData}} +{{$commitComments := $.root.CommitComments}} +{{$ccFile := ""}} +{{if $commitComments}}{{$ccFile = index $commitComments $file.Name}}{{end}} @@ -28,7 +31,7 @@ {{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}} + + + + + {{end}} + {{end}} {{end}} {{end}} {{end}} diff --git a/templates/repo/diff/section_unified.tmpl b/templates/repo/diff/section_unified.tmpl index 6776198b75..4787143f1d 100644 --- a/templates/repo/diff/section_unified.tmpl +++ b/templates/repo/diff/section_unified.tmpl @@ -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}} @@ -33,7 +36,7 @@ {{else}} {{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}} + + + + {{end}} + {{end}} {{end}} {{end}}
{{if $line.LeftIdx}}{{if $leftDiff.EscapeStatus.Escaped}}{{end}}{{end}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}} @@ -43,7 +46,7 @@ {{if $match.RightIdx}}{{if $rightDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $match.RightIdx}}{{end}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}} @@ -60,7 +63,7 @@ {{if $line.LeftIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $line.LeftIdx}}{{end}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 2)) -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) (not (eq .GetType 2)) -}} @@ -75,7 +78,7 @@ {{if $line.RightIdx}}{{if $inlineDiff.EscapeStatus.Escaped}}{{end}}{{end}} {{if $line.RightIdx}}{{end}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles (not (eq .GetType 3)) -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) (not (eq .GetType 3)) -}} @@ -132,6 +135,32 @@
+ {{if $leftCC}} + {{template "repo/diff/commit_conversation" dict "root" $.root "comments" $leftCC}} + {{end}} + + {{if $rightCC}} + {{template "repo/diff/commit_conversation" dict "root" $.root "comments" $rightCC}} + {{end}} +
{{template "repo/diff/section_code" dict "diff" $inlineDiff}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}} @@ -49,5 +52,24 @@
+ {{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}} +