diff --git a/models/issue_comment.go b/models/issue_comment.go index ad1a59e9d38..2a9e8596cb2 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -7,7 +7,6 @@ package models import ( - "bytes" "fmt" "strings" @@ -15,7 +14,6 @@ import ( "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" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -488,32 +486,6 @@ func (c *Comment) UnsignedLine() uint64 { return uint64(c.Line) } -// AsDiff returns c.Patch as *Diff -func (c *Comment) AsDiff() (*Diff, error) { - diff, err := ParsePatch(setting.Git.MaxGitDiffLines, - setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch)) - if err != nil { - return nil, err - } - if len(diff.Files) == 0 { - return nil, fmt.Errorf("no file found for comment ID: %d", c.ID) - } - secs := diff.Files[0].Sections - if len(secs) == 0 { - return nil, fmt.Errorf("no sections found for comment ID: %d", c.ID) - } - return diff, nil -} - -// MustAsDiff executes AsDiff and logs the error instead of returning -func (c *Comment) MustAsDiff() *Diff { - diff, err := c.AsDiff() - if err != nil { - log.Warn("MustAsDiff: %v", err) - } - return diff -} - // CodeCommentURL returns the url to a comment in code func (c *Comment) CodeCommentURL() string { err := c.LoadIssue() @@ -873,59 +845,6 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri return comment, nil } -// CreateCodeComment creates a plain code comment at the specified line / path -func CreateCodeComment(doer *User, repo *Repository, issue *Issue, content, treePath string, line, reviewID int64) (*Comment, error) { - var commitID, patch string - pr, err := GetPullRequestByIssueID(issue.ID) - if err != nil { - return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err) - } - if err := pr.GetBaseRepo(); err != nil { - return nil, fmt.Errorf("GetHeadRepo: %v", err) - } - gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) - if err != nil { - return nil, fmt.Errorf("OpenRepository: %v", err) - } - - // FIXME validate treePath - // Get latest commit referencing the commented line - // No need for get commit for base branch changes - if line > 0 { - commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line)) - if err == nil { - commitID = commit.ID.String() - } else if !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") { - return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err) - } - } - - // Only fetch diff if comment is review comment - if reviewID != 0 { - headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) - if err != nil { - return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) - } - patchBuf := new(bytes.Buffer) - if err := GetRawDiffForFile(gitRepo.Path, pr.MergeBase, headCommitID, RawDiffNormal, treePath, patchBuf); err != nil { - return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, gitRepo.Path, pr.MergeBase, headCommitID, treePath) - } - patch = CutDiffAroundLine(patchBuf, int64((&Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) - } - return CreateComment(&CreateCommentOptions{ - Type: CommentTypeCode, - Doer: doer, - Repo: repo, - Issue: issue, - Content: content, - LineNum: line, - TreePath: treePath, - CommitSHA: commitID, - ReviewID: reviewID, - Patch: patch, - }) -} - // CreateRefComment creates a commit reference comment to issue. func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error { if len(commitSHA) == 0 { diff --git a/models/models.go b/models/models.go index 04acc77aa94..e802a35a777 100644 --- a/models/models.go +++ b/models/models.go @@ -250,3 +250,8 @@ func MaxBatchInsertSize(bean interface{}) int { t := x.TableInfo(bean) return 999 / len(t.ColumnsSeq()) } + +// Count returns records number according struct's fields as database query conditions +func Count(bean interface{}) (int64, error) { + return x.Count(bean) +} diff --git a/modules/repofiles/diff.go b/modules/repofiles/diff.go index 3b5de5fa6f3..c98bbc7684d 100644 --- a/modules/repofiles/diff.go +++ b/modules/repofiles/diff.go @@ -8,10 +8,11 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/services/gitdiff" ) // GetDiffPreview produces and returns diff result of a file which is not yet committed. -func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*models.Diff, error) { +func GetDiffPreview(repo *models.Repository, branch, treePath, content string) (*gitdiff.Diff, error) { if branch == "" { branch = repo.DefaultBranch } diff --git a/modules/repofiles/diff_test.go b/modules/repofiles/diff_test.go index bc7d4ebad66..de5ed1d7548 100644 --- a/modules/repofiles/diff_test.go +++ b/modules/repofiles/diff_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/gitdiff" "github.com/stretchr/testify/assert" ) @@ -25,10 +26,10 @@ func TestGetDiffPreview(t *testing.T) { treePath := "README.md" content := "# repo1\n\nDescription for repo1\nthis is a new line" - expectedDiff := &models.Diff{ + expectedDiff := &gitdiff.Diff{ TotalAddition: 2, TotalDeletion: 1, - Files: []*models.DiffFile{ + Files: []*gitdiff.DiffFile{ { Name: "README.md", OldName: "README.md", @@ -42,10 +43,10 @@ func TestGetDiffPreview(t *testing.T) { IsLFSFile: false, IsRenamed: false, IsSubmodule: false, - Sections: []*models.DiffSection{ + Sections: []*gitdiff.DiffSection{ { Name: "", - Lines: []*models.DiffLine{ + Lines: []*gitdiff.DiffLine{ { LeftIdx: 0, RightIdx: 0, diff --git a/modules/repofiles/temp_repo.go b/modules/repofiles/temp_repo.go index d640ba80b02..f791c3cb965 100644 --- a/modules/repofiles/temp_repo.go +++ b/modules/repofiles/temp_repo.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" ) // TemporaryUploadRepository is a type to wrap our upload repositories as a shallow clone @@ -290,7 +291,7 @@ func (t *TemporaryUploadRepository) Push(doer *models.User, commitHash string, b } // DiffIndex returns a Diff of the current index to the head -func (t *TemporaryUploadRepository) DiffIndex() (diff *models.Diff, err error) { +func (t *TemporaryUploadRepository) DiffIndex() (diff *gitdiff.Diff, err error) { timeout := 5 * time.Minute ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -313,7 +314,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (diff *models.Diff, err error) { pid := process.GetManager().Add(fmt.Sprintf("diffIndex [repo_path: %s]", t.repo.RepoPath()), cmd) defer process.GetManager().Remove(pid) - diff, err = models.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) + diff, err = gitdiff.ParsePatch(setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdout) if err != nil { return nil, fmt.Errorf("ParsePatch: %v", err) } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index e0285808f88..147df3a7880 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -27,6 +27,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/gitdiff" "gopkg.in/editorconfig/editorconfig-core-go.v1" ) @@ -230,6 +231,7 @@ func NewFuncMap() []template.FuncMap { } return float32(n) * 100 / float32(sum) }, + "CommentMustAsDiff": gitdiff.CommentMustAsDiff, }} } diff --git a/routers/repo/commit.go b/routers/repo/commit.go index 79fa024dc9e..c3181cbe468 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" ) const ( @@ -217,7 +218,7 @@ func Diff(ctx *context.Context) { ctx.Data["CommitStatus"] = models.CalcCommitStatus(statuses) - diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName), + diff, err := gitdiff.GetDiffCommit(models.RepoPath(userName, repoName), commitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) if err != nil { @@ -269,10 +270,10 @@ func Diff(ctx *context.Context) { // RawDiff dumps diff results of repository in given commit ID to io.Writer func RawDiff(ctx *context.Context) { - if err := models.GetRawDiff( + if err := gitdiff.GetRawDiff( models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), ctx.Params(":sha"), - models.RawDiffType(ctx.Params(":ext")), + gitdiff.RawDiffType(ctx.Params(":ext")), ctx.Resp, ); err != nil { ctx.ServerError("GetRawDiff", err) diff --git a/routers/repo/compare.go b/routers/repo/compare.go index 193255ca6ca..4f9a918a7c2 100644 --- a/routers/repo/compare.go +++ b/routers/repo/compare.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" ) const ( @@ -230,7 +231,7 @@ func PrepareCompareDiff( return true } - diff, err := models.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), + diff, err := gitdiff.GetDiffRange(models.RepoPath(headUser.Name, headRepo.Name), compareInfo.MergeBase, headCommitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) if err != nil { diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 22ccc7769cf..14d2f508218 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/pull" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/gitdiff" "github.com/unknwon/com" ) @@ -517,7 +518,7 @@ func ViewPullFiles(ctx *context.Context) { ctx.Data["Reponame"] = pull.HeadRepo.Name } - diff, err := models.GetDiffRangeWithWhitespaceBehavior(diffRepoPath, + diff, err := gitdiff.GetDiffRangeWithWhitespaceBehavior(diffRepoPath, startCommitID, endCommitID, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, whitespaceFlags[ctx.Data["WhitespaceBehavior"].(string)]) diff --git a/routers/repo/pull_review.go b/routers/repo/pull_review.go index cf93783042e..d4e3a3326a1 100644 --- a/routers/repo/pull_review.go +++ b/routers/repo/pull_review.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" pull_service "code.gitea.io/gitea/modules/pull" + comment_service "code.gitea.io/gitea/services/comments" ) // CreateCodeComment will create a code comment including an pending review if required @@ -69,7 +70,7 @@ func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) { review.ID = form.Reply } //FIXME check if line, commit and treepath exist - comment, err := models.CreateCodeComment( + comment, err := comment_service.CreateCodeComment( ctx.User, issue.Repo, issue, diff --git a/services/comments/comments.go b/services/comments/comments.go new file mode 100644 index 00000000000..bd261ff0a5e --- /dev/null +++ b/services/comments/comments.go @@ -0,0 +1,69 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package comments + +import ( + "bytes" + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" +) + +// CreateCodeComment creates a plain code comment at the specified line / path +func CreateCodeComment(doer *models.User, repo *models.Repository, issue *models.Issue, content, treePath string, line, reviewID int64) (*models.Comment, error) { + var commitID, patch string + pr, err := models.GetPullRequestByIssueID(issue.ID) + if err != nil { + return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err) + } + if err := pr.GetBaseRepo(); err != nil { + return nil, fmt.Errorf("GetHeadRepo: %v", err) + } + gitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %v", err) + } + + // FIXME validate treePath + // Get latest commit referencing the commented line + // No need for get commit for base branch changes + if line > 0 { + commit, err := gitRepo.LineBlame(pr.GetGitRefName(), gitRepo.Path, treePath, uint(line)) + if err == nil { + commitID = commit.ID.String() + } else if !strings.Contains(err.Error(), "exit status 128 - fatal: no such path") { + return nil, fmt.Errorf("LineBlame[%s, %s, %s, %d]: %v", pr.GetGitRefName(), gitRepo.Path, treePath, line, err) + } + } + + // Only fetch diff if comment is review comment + if reviewID != 0 { + headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil { + return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) + } + patchBuf := new(bytes.Buffer) + if err := gitdiff.GetRawDiffForFile(gitRepo.Path, pr.MergeBase, headCommitID, gitdiff.RawDiffNormal, treePath, patchBuf); err != nil { + return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, gitRepo.Path, pr.MergeBase, headCommitID, treePath) + } + patch = gitdiff.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) + } + return models.CreateComment(&models.CreateCommentOptions{ + Type: models.CommentTypeCode, + Doer: doer, + Repo: repo, + Issue: issue, + Content: content, + LineNum: line, + TreePath: treePath, + CommitSHA: commitID, + ReviewID: reviewID, + Patch: patch, + }) +} diff --git a/models/git_diff.go b/services/gitdiff/gitdiff.go similarity index 94% rename from models/git_diff.go rename to services/gitdiff/gitdiff.go index b8226854094..c2c5675d9fd 100644 --- a/models/git_diff.go +++ b/services/gitdiff/gitdiff.go @@ -1,8 +1,9 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package gitdiff import ( "bufio" @@ -19,6 +20,7 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/highlight" @@ -60,7 +62,7 @@ type DiffLine struct { RightIdx int Type DiffLineType Content string - Comments []*Comment + Comments []*models.Comment } // GetType returns the type of a DiffLine. @@ -254,8 +256,8 @@ type Diff struct { } // LoadComments loads comments into each line -func (diff *Diff) LoadComments(issue *Issue, currentUser *User) error { - allComments, err := FetchCodeComments(issue, currentUser) +func (diff *Diff) LoadComments(issue *models.Issue, currentUser *models.User) error { + allComments, err := models.FetchCodeComments(issue, currentUser) if err != nil { return err } @@ -472,16 +474,16 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D trimLine := strings.Trim(line, "+- ") - if trimLine == LFSMetaFileIdentifier { + if trimLine == models.LFSMetaFileIdentifier { curFileLFSPrefix = true } - if curFileLFSPrefix && strings.HasPrefix(trimLine, LFSMetaFileOidPrefix) { - oid := strings.TrimPrefix(trimLine, LFSMetaFileOidPrefix) + if curFileLFSPrefix && strings.HasPrefix(trimLine, models.LFSMetaFileOidPrefix) { + oid := strings.TrimPrefix(trimLine, models.LFSMetaFileOidPrefix) if len(oid) == 64 { - m := &LFSMetaObject{Oid: oid} - count, err := x.Count(m) + m := &models.LFSMetaObject{Oid: oid} + count, err := models.Count(m) if err == nil && count > 0 { curFile.IsBin = true @@ -798,3 +800,29 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) { return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles) } + +// CommentAsDiff returns c.Patch as *Diff +func CommentAsDiff(c *models.Comment) (*Diff, error) { + diff, err := ParsePatch(setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(c.Patch)) + if err != nil { + return nil, err + } + if len(diff.Files) == 0 { + return nil, fmt.Errorf("no file found for comment ID: %d", c.ID) + } + secs := diff.Files[0].Sections + if len(secs) == 0 { + return nil, fmt.Errorf("no sections found for comment ID: %d", c.ID) + } + return diff, nil +} + +// CommentMustAsDiff executes AsDiff and logs the error instead of returning +func CommentMustAsDiff(c *models.Comment) *Diff { + diff, err := CommentAsDiff(c) + if err != nil { + log.Warn("CommentMustAsDiff: %v", err) + } + return diff +} diff --git a/models/git_diff_test.go b/services/gitdiff/gitdiff_test.go similarity index 86% rename from models/git_diff_test.go rename to services/gitdiff/gitdiff_test.go index bf52095acf1..acf3c6d7bd8 100644 --- a/models/git_diff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -1,10 +1,16 @@ -package models +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitdiff import ( "html/template" "strings" "testing" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" dmp "github.com/sergi/go-diff/diffmatchpatch" @@ -168,23 +174,24 @@ func setupDefaultDiff() *Diff { } } func TestDiff_LoadComments(t *testing.T) { - issue := AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue) - user := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + assert.NoError(t, models.PrepareTestDatabase()) + + issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 2}).(*models.Issue) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) diff := setupDefaultDiff() - assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, diff.LoadComments(issue, user)) assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, 2) } func TestDiffLine_CanComment(t *testing.T) { assert.False(t, (&DiffLine{Type: DiffLineSection}).CanComment()) - assert.False(t, (&DiffLine{Type: DiffLineAdd, Comments: []*Comment{{Content: "bla"}}}).CanComment()) + assert.False(t, (&DiffLine{Type: DiffLineAdd, Comments: []*models.Comment{{Content: "bla"}}}).CanComment()) assert.True(t, (&DiffLine{Type: DiffLineAdd}).CanComment()) assert.True(t, (&DiffLine{Type: DiffLineDel}).CanComment()) assert.True(t, (&DiffLine{Type: DiffLinePlain}).CanComment()) } func TestDiffLine_GetCommentSide(t *testing.T) { - assert.Equal(t, "previous", (&DiffLine{Comments: []*Comment{{Line: -3}}}).GetCommentSide()) - assert.Equal(t, "proposed", (&DiffLine{Comments: []*Comment{{Line: 3}}}).GetCommentSide()) + assert.Equal(t, "previous", (&DiffLine{Comments: []*models.Comment{{Line: -3}}}).GetCommentSide()) + assert.Equal(t, "proposed", (&DiffLine{Comments: []*models.Comment{{Line: 3}}}).GetCommentSide()) } diff --git a/services/gitdiff/main_test.go b/services/gitdiff/main_test.go new file mode 100644 index 00000000000..5ed3c75b705 --- /dev/null +++ b/services/gitdiff/main_test.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitdiff + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models" +) + +func TestMain(m *testing.M) { + models.MainTest(m, filepath.Join("..", "..")) +} diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 68303cf1ca2..70de314c918 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -319,7 +319,7 @@ {{end}} {{$filename}} - {{$diff := ((index $comms 0).MustAsDiff)}} + {{$diff := (CommentMustAsDiff (index $comms 0))}} {{if $diff}} {{$file := (index $diff.Files 0)}}