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 acfc07ff22..4fc3807e2f 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -119,6 +119,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{ @@ -161,6 +163,7 @@ var commentStrings = []string{ "pin", "unpin", "change_time_estimate", + "commit_comment", } func (t CommentType) String() string { @@ -178,7 +181,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 @@ -186,7 +189,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 diff --git a/models/issues/commit_comment.go b/models/issues/commit_comment.go new file mode 100644 index 0000000000..6988e20b9a --- /dev/null +++ b/models/issues/commit_comment.go @@ -0,0 +1,143 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +// CommitComment is a junction table linking a commit (repo + SHA) to +// a Comment entry. The comment content, tree_path, line, poster, etc. +// are stored in the Comment table with Type = CommentTypeCommitComment. +type CommitComment struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + CommitSHA string `xorm:"VARCHAR(64) INDEX NOT NULL"` + CommentID int64 `xorm:"UNIQUE NOT NULL"` +} + +func init() { + db.RegisterModel(new(CommitComment)) +} + +// 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) { + var commentIDs []int64 + if err := db.GetEngine(ctx).Cols("comment_id").Table("commit_comment"). + Where("repo_id = ? AND commit_sha = ?", repoID, commitSHA). + Find(&commentIDs); err != nil { + return nil, err + } + + if len(commentIDs) == 0 { + return nil, nil + } + + comments := make([]*Comment, 0, len(commentIDs)) + if err := db.GetEngine(ctx). + In("id", commentIDs). + 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 and a +// corresponding CommitComment junction record, within a transaction. +func CreateCommitComment(ctx context.Context, repoID int64, commitSHA string, comment *Comment) error { + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := db.GetEngine(ctx).Insert(comment); err != nil { + return err + } + + ref := &CommitComment{ + RepoID: repoID, + CommitSHA: commitSHA, + CommentID: comment.ID, + } + _, err := db.GetEngine(ctx).Insert(ref) + return err + }) +} + +// DeleteCommitComment deletes both the junction record and the Comment entry. +func DeleteCommitComment(ctx context.Context, commentID int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := db.GetEngine(ctx).Where("comment_id = ?", commentID).Delete(&CommitComment{}); err != nil { + return err + } + _, err := db.GetEngine(ctx).ID(commentID).Delete(&Comment{}) + return err + }) +} + +// GetCommitCommentByID returns a commit comment by loading the Comment entry, +// verifying it belongs to the given repository via the junction table. +func GetCommitCommentByID(ctx context.Context, repoID, commentID int64) (*Comment, error) { + exists, err := db.GetEngine(ctx).Table("commit_comment"). + Where("repo_id = ? AND comment_id = ?", repoID, commentID). + Exist() + if err != nil { + return nil, err + } + if !exists { + return nil, db.ErrNotExist{Resource: "CommitComment", ID: commentID} + } + + c := &Comment{} + has, err := db.GetEngine(ctx).ID(commentID).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/migrations/migrations.go b/models/migrations/migrations.go index c3a8f08b5d..4208dc4731 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -409,6 +409,7 @@ func prepareMigrationTasks() []*migration { // Gitea 1.26.0 ends at migration ID number 330 (database version 331) newMigration(331, "Add ActionRunAttempt model and related action fields", v1_27.AddActionRunAttemptModel), + newMigration(332, "Add commit comment table", v1_27.AddCommitCommentTable), } return preparedMigrations } diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go deleted file mode 100644 index a0225eb774..0000000000 --- a/models/migrations/v1_26/v326_test.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_26 - -import ( - "testing" - - "code.gitea.io/gitea/models/migrations/migrationtest" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - - _ "code.gitea.io/gitea/models/actions" - _ "code.gitea.io/gitea/models/git" - _ "code.gitea.io/gitea/models/repo" - - "github.com/stretchr/testify/require" - "xorm.io/xorm" -) - -func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) { - defer test.MockVariableValue(&setting.AppSubURL, "")() - - type Repository struct { - ID int64 `xorm:"pk autoincr"` - OwnerName string - Name string - } - - type ActionRun struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"index"` - Index int64 - CommitSHA string `xorm:"commit_sha"` - Event string - TriggerEvent string - EventPayload string `xorm:"LONGTEXT"` - } - - type ActionRunJob struct { - ID int64 `xorm:"pk autoincr"` - RunID int64 `xorm:"index"` - } - - type CommitStatus struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"index"` - SHA string - TargetURL string - } - - type CommitStatusSummary struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"index"` - SHA string `xorm:"VARCHAR(64) NOT NULL"` - State string `xorm:"VARCHAR(7) NOT NULL"` - TargetURL string - } - - x, deferable := migrationtest.PrepareTestEnv(t, 0, - new(Repository), - new(ActionRun), - new(ActionRunJob), - new(CommitStatus), - new(CommitStatusSummary), - ) - defer deferable() - - require.NoError(t, FixCommitStatusTargetURLToUseRunAndJobID(x)) - - cases := []struct { - table string - id int64 - want string - }{ - // Legacy URLs for runs whose resolved run IDs are below the threshold should be rewritten. - {table: "commit_status", id: 10010, want: "/testuser/repo1/actions/runs/990/jobs/997"}, - {table: "commit_status", id: 10011, want: "/testuser/repo1/actions/runs/990/jobs/998"}, - {table: "commit_status", id: 10012, want: "/testuser/repo1/actions/runs/991/jobs/1997"}, - - // Runs whose resolved IDs are above the threshold are intentionally left unchanged. - {table: "commit_status", id: 10013, want: "/testuser/repo1/actions/runs/9/jobs/0"}, - - // URLs that do not resolve cleanly as legacy Actions URLs should remain untouched. - {table: "commit_status", id: 10014, want: "/otheruser/badrepo/actions/runs/7/jobs/0"}, - {table: "commit_status", id: 10015, want: "/testuser/repo1/actions/runs/10/jobs/0"}, - {table: "commit_status", id: 10016, want: "/testuser/repo1/actions/runs/7/jobs/3"}, - {table: "commit_status", id: 10017, want: "https://ci.example.com/build/123"}, - - // Already ID-based URLs are valid inputs and should not be rewritten again. - {table: "commit_status", id: 10018, want: "/testuser/repo1/actions/runs/990/jobs/997"}, - - // The same rewrite rules apply to commit_status_summary rows. - {table: "commit_status_summary", id: 10020, want: "/testuser/repo1/actions/runs/990/jobs/997"}, - {table: "commit_status_summary", id: 10021, want: "/testuser/repo1/actions/runs/9/jobs/0"}, - } - - for _, tc := range cases { - assertTargetURL(t, x, tc.table, tc.id, tc.want) - } -} - -func assertTargetURL(t *testing.T, x *xorm.Engine, table string, id int64, want string) { - t.Helper() - - var row struct { - TargetURL string - } - has, err := x.Table(table).Where("id=?", id).Cols("target_url").Get(&row) - require.NoError(t, err) - require.Truef(t, has, "row not found: table=%s id=%d", table, id) - require.Equal(t, want, row.TargetURL) -} diff --git a/models/migrations/v1_27/v332.go b/models/migrations/v1_27/v332.go new file mode 100644 index 0000000000..9107af3cf5 --- /dev/null +++ b/models/migrations/v1_27/v332.go @@ -0,0 +1,27 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddCommitCommentTable(x *xorm.Engine) error { + type CommitComment struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + CommitSHA string `xorm:"VARCHAR(64) INDEX NOT NULL"` + TreePath string `xorm:"VARCHAR(4000) NOT NULL"` + Line int64 `xorm:"NOT NULL"` + PosterID int64 `xorm:"INDEX NOT NULL"` + Content string `xorm:"LONGTEXT NOT NULL"` + Patch string `xorm:"LONGTEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + return x.Sync(new(CommitComment)) +} diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 6c973696ff..e85698044e 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -26,6 +26,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" @@ -413,6 +414,35 @@ 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 + for _, fcc := range commitComments { + for _, comments := range fcc.Left { + 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 _, comments := range fcc.Right { + 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) + } + } + } + } + ctx.Data["CommitComments"] = commitComments + ctx.Data["CanComment"] = ctx.Doer != nil && ctx.Repo.Permission.CanRead(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..5b32f7e003 --- /dev/null +++ b/routers/web/repo/commit_comment.go @@ -0,0 +1,157 @@ +// 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" + "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{ + Type: issues_model.CommentTypeCommitComment, + PosterID: ctx.Doer.ID, + Poster: ctx.Doer, + CommitSHA: fullSHA, + TreePath: treePath, + Line: line, + Content: content, + Patch: patch, + } + + if err := issues_model.CreateCommitComment(ctx, ctx.Repo.Repository.ID, fullSHA, 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 + } + + if comment.PosterID != ctx.Doer.ID && !ctx.Repo.Permission.IsAdmin() { + ctx.JSONError("permission denied") + return + } + + if err := issues_model.DeleteCommitComment(ctx, 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 ecd75250d2..5fd42cf50a 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1698,6 +1698,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 463575b84d..0fafaa5f0e 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..64dc7a1763 --- /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 (eq $.root.SignedUserID .PosterID)}} +
+ {{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..9577091693 --- /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" $ "comments" .comments}} +
+
+
+
+ + +
+
+ {{if and $.SignedUserID (not $.Repository.IsArchived)}} + {{template "repo/diff/commit_comment_form" dict "hidden" true "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 c13b205518..401bdea6af 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 4dee648cdd..278c465838 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}} @@ -31,7 +34,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}}{{ctx.RenderUtils.RenderUnicodeEscapeToggleButton $leftDiff.EscapeStatus}}{{end}} - {{- if and $.root.SignedUserID $.root.PageIsPullFiles -}} + {{- if and $.root.SignedUserID (or $.root.PageIsPullFiles $.root.PageIsDiff) -}} @@ -43,7 +46,7 @@ {{if $match.RightIdx}}{{ctx.RenderUtils.RenderUnicodeEscapeToggleButton $rightDiff.EscapeStatus}}{{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}}{{ctx.RenderUtils.RenderUnicodeEscapeToggleButton $inlineDiff.EscapeStatus}}{{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}}{{ctx.RenderUtils.RenderUnicodeEscapeToggleButton $inlineDiff.EscapeStatus}}{{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 "comments" $leftCC}} + {{end}} + + {{if $rightCC}} + {{template "repo/diff/commit_conversation" dict "." $.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) -}} @@ -47,5 +50,24 @@
+ {{if $rightCC}} + {{template "repo/diff/commit_conversation" dict "." $.root "comments" $rightCC}} + {{end}} + {{if $leftCC}} + {{template "repo/diff/commit_conversation" dict "." $.root "comments" $leftCC}} + {{end}} +