diff --git a/models/issues/comment.go b/models/issues/comment.go index be020b2e1fb2d..5fd8e0b5f0d0c 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -260,6 +260,8 @@ type Comment struct { TreePath string Content string `xorm:"LONGTEXT"` RenderedContent string `xorm:"-"` + StartLine int64 // - previous line / + proposed line + IsMultiLine bool `xorm:"NOT NULL DEFAULT false"` // Path represents the 4 lines of code cemented by this comment Patch string `xorm:"-"` @@ -724,6 +726,14 @@ func (c *Comment) UnsignedLine() uint64 { return uint64(c.Line) } +// UnsignedStartLine returns the start line of the code comment without + or - +func (c *Comment) UnsignedStartLine() uint64 { + if c.StartLine < 0 { + return uint64(c.StartLine * -1) + } + return uint64(c.StartLine) +} + // CodeCommentLink returns the url to a comment in code func (c *Comment) CodeCommentLink() string { err := c.LoadIssue(db.DefaultContext) @@ -800,6 +810,8 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, CommitID: opts.CommitID, CommitSHA: opts.CommitSHA, Line: opts.LineNum, + StartLine: opts.StartLineNum, + IsMultiLine: opts.IsMultiLine, Content: opts.Content, OldTitle: opts.OldTitle, NewTitle: opts.NewTitle, @@ -976,6 +988,8 @@ type CreateCommentOptions struct { CommitSHA string Patch string LineNum int64 + StartLineNum int64 + IsMultiLine bool TreePath string ReviewID int64 Content string diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index b2140a1eb1327..32bc52f6f974b 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -523,6 +523,8 @@ var migrations = []Migration{ NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable), // v270 -> v271 NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo), + // v271 -> v272 + NewMigration("Add start_line and is_multi_line Column in comment table", v1_21.AddStartLineAndIsMultiLineToComment), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go new file mode 100644 index 0000000000000..438fc474de250 --- /dev/null +++ b/models/migrations/v1_21/v271.go @@ -0,0 +1,17 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddStartLineAndIsMultiLineToComment(x *xorm.Engine) error { + type Comment struct { + StartLine int64 // - previous line / + proposed line + IsMultiLine bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync(new(Comment)) +} diff --git a/modules/structs/pull_review.go b/modules/structs/pull_review.go index 810be8f521d94..78485678be58d 100644 --- a/modules/structs/pull_review.go +++ b/modules/structs/pull_review.go @@ -68,6 +68,13 @@ type PullReviewComment struct { HTMLURL string `json:"html_url"` HTMLPullURL string `json:"pull_request_url"` + + // The line of the blob to which the comment applies. The last line of the range for a multi-line comment. + Line uint64 `json:"line"` + // The side of the diff to which the comment applies. The side of the last line of the range for a multi-line comment. + Side string `json:"side"` + // The first line of the range for a multi-line comment. + StartLine uint64 `json:"start_line,omitempty"` } // CreatePullReviewOptions are options to create a pull review @@ -83,10 +90,15 @@ type CreatePullReviewComment struct { // the tree path Path string `json:"path"` Body string `json:"body"` - // if comment to old file line or 0 + // if comment to old file line or 0 (This parameter is deprecated. Suggest use line and side instead) OldLineNum int64 `json:"old_position"` - // if comment to new file line or 0 + // if comment to new file line or 0 (This parameter is deprecated. Suggest use line and side instead) NewLineNum int64 `json:"new_position"` + // The line of the blob in the pull request diff that the comment applies to. For a multi-line comment, the last line of the range that your comment applies to. + Line *int64 `json:"line"` + // Can be LEFT or RIGHT. Use LEFT for deletions that appear in red. Use RIGHT for additions that appear in green. + Side *string `json:"side"` + StartLine *int64 `json:"start_line"` } // SubmitPullReviewOptions are options to submit a pending pull review diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index a568cd565a768..d59e73d01090e 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -353,11 +353,29 @@ func CreatePullReview(ctx *context.APIContext) { line = c.OldLineNum * -1 } + if c.Side != nil && c.Line != nil && *c.Line > 0 { + line = *c.Line + if strings.ToUpper(*c.Side) == "LEFT" { + line *= -1 + } + } + + var startLine int64 + isMultiLine := c.Side != nil && c.StartLine != nil && *c.StartLine > 0 + if isMultiLine { + startLine = *c.StartLine + if strings.ToUpper(*c.Side) == "LEFT" { + startLine *= -1 + } + } + if _, err := pull_service.CreateCodeComment(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, line, + startLine, + isMultiLine, c.Body, c.Path, true, // pending review diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index c2271750c4d1d..fbfa9b93acbd3 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -78,6 +78,8 @@ func CreateCodeComment(ctx *context.Context) { ctx.Repo.GitRepo, issue, signedLine, + 0, + false, form.Content, form.TreePath, !form.SingleReview, diff --git a/services/convert/pull_review.go b/services/convert/pull_review.go index 5d5d5d883c997..e6f90f35fe1d3 100644 --- a/services/convert/pull_review.go +++ b/services/convert/pull_review.go @@ -107,10 +107,16 @@ func ToPullReviewCommentList(ctx context.Context, review *issues_model.Review, d } if comment.Line < 0 { + apiComment.Side = "LEFT" apiComment.OldLineNum = comment.UnsignedLine() } else { + apiComment.Side = "RIGHT" apiComment.LineNum = comment.UnsignedLine() } + if comment.IsMultiLine { + apiComment.StartLine = comment.UnsignedStartLine() + } + apiComment.Line = comment.UnsignedLine() apiComments = append(apiComments, apiComment) } } diff --git a/services/mailer/incoming/incoming_handler.go b/services/mailer/incoming/incoming_handler.go index b594e35189bf5..b892aa0aee9c3 100644 --- a/services/mailer/incoming/incoming_handler.go +++ b/services/mailer/incoming/incoming_handler.go @@ -125,6 +125,8 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u nil, issue, comment.Line, + 0, + false, content.Content, comment.TreePath, false, // not pending review but a single review diff --git a/services/pull/review.go b/services/pull/review.go index 59cc607912507..1f698b68071b9 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -71,7 +71,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis } // CreateCodeComment creates a comment on the code line -func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line int64, content, treePath string, pendingReview bool, replyReviewID int64, latestCommitID string) (*issues_model.Comment, error) { +func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue, line, startLine int64, isMultiLine bool, content, treePath string, pendingReview bool, replyReviewID int64, latestCommitID string) (*issues_model.Comment, error) { var ( existsReview bool err error @@ -103,7 +103,9 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git. content, treePath, line, + startLine, replyReviewID, + isMultiLine, ) if err != nil { return nil, err @@ -143,7 +145,9 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git. content, treePath, line, + startLine, review.ID, + isMultiLine, ) if err != nil { return nil, err @@ -162,7 +166,7 @@ func CreateCodeComment(ctx context.Context, doer *user_model.User, gitRepo *git. } // createCodeComment creates a plain code comment at the specified line / path -func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, treePath string, line, reviewID int64) (*issues_model.Comment, error) { +func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, treePath string, line, startLine, reviewID int64, isMultiLine bool) (*issues_model.Comment, error) { var commitID, patch string if err := issue.LoadPullRequest(ctx); err != nil { return nil, fmt.Errorf("LoadPullRequest: %w", err) @@ -177,6 +181,10 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo } defer closer.Close() + if isMultiLine && (startLine*line < 0 || startLine > line) { + return nil, fmt.Errorf("not supported multi line format: %d, %d", startLine, line) + } + invalidated := false head := pr.GetGitRefName() if line > 0 { @@ -242,24 +250,33 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo _ = writer.Close() }() - patch, err = git.CutDiffAroundLine(reader, int64((&issues_model.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) + var cutLineNum int + if isMultiLine { + cutLineNum = int(line - startLine + 1) + } else { + cutLineNum = setting.UI.CodeCommentLines + } + + patch, err = git.CutDiffAroundLine(reader, int64((&issues_model.Comment{Line: line}).UnsignedLine()), line < 0, cutLineNum) if err != nil { log.Error("Error whilst generating patch: %v", err) return nil, err } } return issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeCode, - Doer: doer, - Repo: repo, - Issue: issue, - Content: content, - LineNum: line, - TreePath: treePath, - CommitSHA: commitID, - ReviewID: reviewID, - Patch: patch, - Invalidated: invalidated, + Type: issues_model.CommentTypeCode, + Doer: doer, + Repo: repo, + Issue: issue, + Content: content, + LineNum: line, + StartLineNum: startLine, + IsMultiLine: isMultiLine, + TreePath: treePath, + CommitSHA: commitID, + ReviewID: reviewID, + Patch: patch, + Invalidated: invalidated, }) } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8cf5332bafc48..65fb15217e334 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -17222,14 +17222,20 @@ "type": "string", "x-go-name": "Body" }, + "line": { + "description": "The line of the blob in the pull request diff that the comment applies to. For a multi-line comment, the last line of the range that your comment applies to.", + "type": "integer", + "format": "int64", + "x-go-name": "Line" + }, "new_position": { - "description": "if comment to new file line or 0", + "description": "if comment to new file line or 0 (This parameter is deprecated. Suggest use line and side instead)", "type": "integer", "format": "int64", "x-go-name": "NewLineNum" }, "old_position": { - "description": "if comment to old file line or 0", + "description": "if comment to old file line or 0 (This parameter is deprecated. Suggest use line and side instead)", "type": "integer", "format": "int64", "x-go-name": "OldLineNum" @@ -17238,6 +17244,16 @@ "description": "the tree path", "type": "string", "x-go-name": "Path" + }, + "side": { + "description": "Can be LEFT or RIGHT. Use LEFT for deletions that appear in red. Use RIGHT for additions that appear in green.", + "type": "string", + "x-go-name": "Side" + }, + "start_line": { + "type": "integer", + "format": "int64", + "x-go-name": "StartLine" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -20687,6 +20703,12 @@ "format": "int64", "x-go-name": "ID" }, + "line": { + "description": "The line of the blob to which the comment applies. The last line of the range for a multi-line comment.", + "type": "integer", + "format": "uint64", + "x-go-name": "Line" + }, "original_commit_id": { "type": "string", "x-go-name": "OrigCommitID" @@ -20717,6 +20739,17 @@ "resolver": { "$ref": "#/definitions/User" }, + "side": { + "description": "The side of the diff to which the comment applies. The side of the last line of the range for a multi-line comment.", + "type": "string", + "x-go-name": "Side" + }, + "start_line": { + "description": "The first line of the range for a multi-line comment.", + "type": "integer", + "format": "uint64", + "x-go-name": "StartLine" + }, "updated_at": { "type": "string", "format": "date-time",