@@ -30,8 +30,11 @@ import (
3030 "code.gitea.io/gitea/modules/setting"
3131 "code.gitea.io/gitea/modules/templates"
3232 "code.gitea.io/gitea/modules/util"
33+ "code.gitea.io/gitea/modules/web"
3334 asymkey_service "code.gitea.io/gitea/services/asymkey"
3435 "code.gitea.io/gitea/services/context"
36+ "code.gitea.io/gitea/services/context/upload"
37+ "code.gitea.io/gitea/services/forms"
3538 git_service "code.gitea.io/gitea/services/git"
3639 "code.gitea.io/gitea/services/gitdiff"
3740 repo_service "code.gitea.io/gitea/services/repository"
@@ -417,6 +420,25 @@ func Diff(ctx *context.Context) {
417420 ctx .Data ["MergedPRIssueNumber" ] = pr .Index
418421 }
419422
423+ // Load commit comments for inline display
424+ comments , err := issues_model .FindCommitComments (ctx , ctx .Repo .Repository .ID , commitID )
425+ if err != nil {
426+ log .Error ("FindCommitComments: %v" , err )
427+ } else {
428+ if err := comments .LoadPosters (ctx ); err != nil {
429+ log .Error ("LoadPosters: %v" , err )
430+ }
431+ if err := comments .LoadAttachments (ctx ); err != nil {
432+ log .Error ("LoadAttachments: %v" , err )
433+ }
434+ ctx .Data ["CommitComments" ] = comments
435+ }
436+
437+ // Mark this as a commit page to enable comment UI
438+ ctx .Data ["PageIsCommit" ] = true
439+ ctx .Data ["IsAttachmentEnabled" ] = setting .Attachment .Enabled
440+ upload .AddUploadContext (ctx , "comment" )
441+
420442 ctx .HTML (http .StatusOK , tplCommitPage )
421443}
422444
@@ -469,3 +491,135 @@ func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_m
469491 }
470492 return commits , nil
471493}
494+
495+ // RenderNewCommitCodeCommentForm renders the form for creating a new commit code comment
496+ func RenderNewCommitCodeCommentForm (ctx * context.Context ) {
497+ ctx .Data ["PageIsCommit" ] = true
498+ ctx .Data ["AfterCommitID" ] = ctx .PathParam ("sha" )
499+ ctx .Data ["IsAttachmentEnabled" ] = setting .Attachment .Enabled
500+ upload .AddUploadContext (ctx , "comment" )
501+ // Use the same template as PR new comments (defined in pull_review.go)
502+ ctx .HTML (http .StatusOK , "repo/diff/new_comment" )
503+ }
504+
505+ // CreateCommitCodeComment creates an inline comment on a commit
506+ func CreateCommitCodeComment (ctx * context.Context ) {
507+ form := web .GetForm (ctx ).(* forms.CodeCommentForm )
508+ commitSHA := ctx .PathParam ("sha" )
509+
510+ if ctx .Written () {
511+ return
512+ }
513+
514+ if ctx .HasError () {
515+ ctx .Flash .Error (ctx .Data ["ErrorMsg" ].(string ))
516+ ctx .Redirect (fmt .Sprintf ("%s/commit/%s" , ctx .Repo .RepoLink , commitSHA ))
517+ return
518+ }
519+
520+ // Convert line to signed line (negative for previous side)
521+ signedLine := form .Line
522+ if form .Side == "previous" {
523+ signedLine *= - 1
524+ }
525+
526+ var attachments []string
527+ if setting .Attachment .Enabled {
528+ attachments = form .Files
529+ }
530+
531+ // Create the comment using the service layer
532+ comment , err := repo_service .CreateCommitCodeComment (
533+ ctx ,
534+ ctx .Doer ,
535+ ctx .Repo .Repository ,
536+ ctx .Repo .GitRepo ,
537+ commitSHA ,
538+ signedLine ,
539+ form .Content ,
540+ form .TreePath ,
541+ attachments ,
542+ )
543+ if err != nil {
544+ ctx .ServerError ("CreateCommitCodeComment" , err )
545+ return
546+ }
547+
548+ log .Trace ("Commit comment created: %d for commit %s in %-v" , comment .ID , commitSHA , ctx .Repo .Repository )
549+
550+ // Render the comment
551+ ctx .Data ["Comment" ] = comment
552+ ctx .Data ["IsAttachmentEnabled" ] = setting .Attachment .Enabled
553+ upload .AddUploadContext (ctx , "comment" )
554+
555+ ctx .JSON (http .StatusOK , map [string ]any {
556+ "ok" : true ,
557+ "comment" : comment ,
558+ })
559+ }
560+
561+ // UpdateCommitCodeComment updates an existing commit inline comment
562+ func UpdateCommitCodeComment (ctx * context.Context ) {
563+ form := web .GetForm (ctx ).(* forms.CodeCommentForm )
564+ commentID := ctx .PathParamInt64 (":id" )
565+
566+ comment , err := issues_model .GetCommentByID (ctx , commentID )
567+ if err != nil {
568+ ctx .ServerError ("GetCommentByID" , err )
569+ return
570+ }
571+
572+ // Verify this is a commit comment
573+ if comment .Type != issues_model .CommentTypeCode || comment .CommitSHA == "" {
574+ ctx .NotFound (errors .New ("not a commit code comment" ))
575+ return
576+ }
577+
578+ // Verify the comment belongs to this repository
579+ if comment .PosterID != ctx .Doer .ID {
580+ ctx .HTTPError (http .StatusForbidden )
581+ return
582+ }
583+
584+ var attachments []string
585+ if setting .Attachment .Enabled {
586+ attachments = form .Files
587+ }
588+
589+ // Update the comment
590+ if err := repo_service .UpdateCommitCodeComment (ctx , ctx .Doer , comment , form .Content , attachments ); err != nil {
591+ ctx .ServerError ("UpdateCommitCodeComment" , err )
592+ return
593+ }
594+
595+ ctx .JSON (http .StatusOK , map [string ]any {
596+ "ok" : true ,
597+ })
598+ }
599+
600+ // DeleteCommitCodeComment deletes a commit inline comment
601+ func DeleteCommitCodeComment (ctx * context.Context ) {
602+ commentID := ctx .PathParamInt64 (":id" )
603+
604+ comment , err := issues_model .GetCommentByID (ctx , commentID )
605+ if err != nil {
606+ ctx .ServerError ("GetCommentByID" , err )
607+ return
608+ }
609+
610+ // Verify this is a commit comment
611+ if comment .Type != issues_model .CommentTypeCode || comment .CommitSHA == "" {
612+ ctx .NotFound (errors .New ("not a commit code comment" ))
613+ return
614+ }
615+
616+ // Delete the comment
617+ if err := repo_service .DeleteCommitCodeComment (ctx , ctx .Doer , comment ); err != nil {
618+ ctx .ServerError ("DeleteCommitCodeComment" , err )
619+ return
620+ }
621+
622+ ctx .JSON (http .StatusOK , map [string ]any {
623+ "ok" : true ,
624+ })
625+ }
0 commit comments