mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add support for invalidating comments
Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
		| @@ -48,7 +48,7 @@ func TestDiff_LoadComments(t *testing.T) { | |||||||
| 					{ | 					{ | ||||||
| 						Lines: []*DiffLine{ | 						Lines: []*DiffLine{ | ||||||
| 							{ | 							{ | ||||||
| 								LeftIdx: 4, | 								LeftIdx:  4, | ||||||
| 								RightIdx: 4, | 								RightIdx: 4, | ||||||
| 							}, | 							}, | ||||||
| 						}, | 						}, | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ package models | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/git" | ||||||
| 	"code.gitea.io/gitea/modules/markup/markdown" | 	"code.gitea.io/gitea/modules/markup/markdown" | ||||||
| 	"github.com/Unknwon/com" | 	"github.com/Unknwon/com" | ||||||
| 	"github.com/go-xorm/builder" | 	"github.com/go-xorm/builder" | ||||||
| @@ -122,8 +124,9 @@ type Comment struct { | |||||||
| 	// For view issue page. | 	// For view issue page. | ||||||
| 	ShowTag CommentTag `xorm:"-"` | 	ShowTag CommentTag `xorm:"-"` | ||||||
|  |  | ||||||
| 	Review   *Review `xorm:"-"` | 	Review      *Review `xorm:"-"` | ||||||
| 	ReviewID int64 | 	ReviewID    int64 | ||||||
|  | 	Invalidated bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // AfterLoad is invoked from XORM after setting the values of all fields of this object. | // AfterLoad is invoked from XORM after setting the values of all fields of this object. | ||||||
| @@ -339,6 +342,40 @@ func (c *Comment) LoadReview() error { | |||||||
| 	return c.loadReview(x) | 	return c.loadReview(x) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Comment) getPathAndFile(repoPath string) (string, string) { | ||||||
|  | 	p := path.Dir(c.TreePath) | ||||||
|  | 	if p == "." { | ||||||
|  | 		p = "" | ||||||
|  | 	} | ||||||
|  | 	p = fmt.Sprintf("%s/%s", repoPath, p) | ||||||
|  | 	file := path.Base(c.TreePath) | ||||||
|  | 	return p, file | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Comment) checkInvalidation(e Engine, repo *git.Repository, branch string) error { | ||||||
|  | 	p, file := c.getPathAndFile(repo.Path) | ||||||
|  | 	// FIXME differentiate between previous and proposed line | ||||||
|  | 	var line = c.Line | ||||||
|  | 	if line < 0 { | ||||||
|  | 		line *= -1 | ||||||
|  | 	} | ||||||
|  | 	commit, err := repo.LineBlame(branch, p, file, uint(line)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if c.CommitSHA != commit.ID.String() { | ||||||
|  | 		c.Invalidated = true | ||||||
|  | 		return UpdateComment(c) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CheckInvalidation checks if the line of code comment got changed by another commit. | ||||||
|  | // If the line got changed the comment is going to be invalidated. | ||||||
|  | func (c *Comment) CheckInvalidation(repo *git.Repository, branch string) error { | ||||||
|  | 	return c.checkInvalidation(x, repo, branch) | ||||||
|  | } | ||||||
|  |  | ||||||
| func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { | func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { | ||||||
| 	var LabelID int64 | 	var LabelID int64 | ||||||
| 	if opts.Label != nil { | 	if opts.Label != nil { | ||||||
| @@ -622,7 +659,29 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri | |||||||
| } | } | ||||||
|  |  | ||||||
| // CreateCodeComment creates a plain code comment at the specified line / path | // CreateCodeComment creates a plain code comment at the specified line / path | ||||||
| func CreateCodeComment(doer *User, repo *Repository, issue *Issue, commitSHA, content, treePath string, line, reviewID int64) (*Comment, error) { | func CreateCodeComment(doer *User, repo *Repository, issue *Issue, content, treePath string, line, reviewID int64) (*Comment, error) { | ||||||
|  | 	pr, err := GetPullRequestByIssueID(issue.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("GetPullRequestByIssueID: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if err := pr.GetHeadRepo(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("GetHeadRepo: %v", err) | ||||||
|  | 	} | ||||||
|  | 	gitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("OpenRepository: %v", err) | ||||||
|  | 	} | ||||||
|  | 	dummyComment := &Comment{Line: line, TreePath: treePath} | ||||||
|  | 	p, file := dummyComment.getPathAndFile(gitRepo.Path) | ||||||
|  | 	// FIXME differentiate between previous and proposed line | ||||||
|  | 	var gitLine = line | ||||||
|  | 	if gitLine < 0 { | ||||||
|  | 		gitLine *= -1 | ||||||
|  | 	} | ||||||
|  | 	commit, err := gitRepo.LineBlame(pr.HeadBranch, p, file, uint(gitLine)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
| 	return CreateComment(&CreateCommentOptions{ | 	return CreateComment(&CreateCommentOptions{ | ||||||
| 		Type:      CommentTypeCode, | 		Type:      CommentTypeCode, | ||||||
| 		Doer:      doer, | 		Doer:      doer, | ||||||
| @@ -631,7 +690,7 @@ func CreateCodeComment(doer *User, repo *Repository, issue *Issue, commitSHA, co | |||||||
| 		Content:   content, | 		Content:   content, | ||||||
| 		LineNum:   line, | 		LineNum:   line, | ||||||
| 		TreePath:  treePath, | 		TreePath:  treePath, | ||||||
| 		CommitSHA: commitSHA, | 		CommitSHA: commit.ID.String(), | ||||||
| 		ReviewID:  reviewID, | 		ReviewID:  reviewID, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @@ -792,14 +851,21 @@ func DeleteComment(comment *Comment) error { | |||||||
|  |  | ||||||
| func fetchCodeComments(e Engine, issue *Issue, currentUser *User) (map[string]map[int64][]*Comment, error) { | func fetchCodeComments(e Engine, issue *Issue, currentUser *User) (map[string]map[int64][]*Comment, error) { | ||||||
| 	pathToLineToComment := make(map[string]map[int64][]*Comment) | 	pathToLineToComment := make(map[string]map[int64][]*Comment) | ||||||
| 	comments, err := findComments(e, FindCommentsOptions{ |  | ||||||
|  | 	//Find comments | ||||||
|  | 	opts := FindCommentsOptions{ | ||||||
| 		Type:    CommentTypeCode, | 		Type:    CommentTypeCode, | ||||||
| 		IssueID: issue.ID, | 		IssueID: issue.ID, | ||||||
| 	}) | 	} | ||||||
| 	if err != nil { | 	var comments []*Comment | ||||||
|  | 	if err := e.Where(opts.toConds().And(builder.Eq{"invalidated": false})). | ||||||
|  | 		Asc("comment.created_unix"). | ||||||
|  | 		Asc("comment.id"). | ||||||
|  | 		Find(&comments); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if err = issue.loadRepo(e); err != nil { |  | ||||||
|  | 	if err := issue.loadRepo(e); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	// Find all reviews by ReviewID | 	// Find all reviews by ReviewID | ||||||
| @@ -810,7 +876,7 @@ func fetchCodeComments(e Engine, issue *Issue, currentUser *User) (map[string]ma | |||||||
| 			ids = append(ids, comment.ReviewID) | 			ids = append(ids, comment.ReviewID) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if err = e.In("id", ids).Find(&reviews); err != nil { | 	if err := e.In("id", ids).Find(&reviews); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	for _, comment := range comments { | 	for _, comment := range comments { | ||||||
|   | |||||||
| @@ -1063,10 +1063,7 @@ func (prs PullRequestList) loadAttributes(e Engine) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Load issues. | 	// Load issues. | ||||||
| 	issueIDs := make([]int64, 0, len(prs)) | 	issueIDs := prs.getIssueIDs() | ||||||
| 	for i := range prs { |  | ||||||
| 		issueIDs = append(issueIDs, prs[i].IssueID) |  | ||||||
| 	} |  | ||||||
| 	issues := make([]*Issue, 0, len(issueIDs)) | 	issues := make([]*Issue, 0, len(issueIDs)) | ||||||
| 	if err := e. | 	if err := e. | ||||||
| 		Where("id > 0"). | 		Where("id > 0"). | ||||||
| @@ -1085,11 +1082,44 @@ func (prs PullRequestList) loadAttributes(e Engine) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (prs PullRequestList) getIssueIDs() []int64 { | ||||||
|  | 	issueIDs := make([]int64, 0, len(prs)) | ||||||
|  | 	for i := range prs { | ||||||
|  | 		issueIDs = append(issueIDs, prs[i].IssueID) | ||||||
|  | 	} | ||||||
|  | 	return issueIDs | ||||||
|  | } | ||||||
|  |  | ||||||
| // LoadAttributes load all the prs attributes | // LoadAttributes load all the prs attributes | ||||||
| func (prs PullRequestList) LoadAttributes() error { | func (prs PullRequestList) LoadAttributes() error { | ||||||
| 	return prs.loadAttributes(x) | 	return prs.loadAttributes(x) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (prs PullRequestList) invalidateCodeComments(e Engine, repo *git.Repository, branch string) error { | ||||||
|  | 	if len(prs) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	issueIDs := prs.getIssueIDs() | ||||||
|  | 	var codeComments []*Comment | ||||||
|  | 	if err := e. | ||||||
|  | 		Where("type = ? and invalidated = ?", CommentTypeCode, false). | ||||||
|  | 		In("issue_id", issueIDs). | ||||||
|  | 		Find(&codeComments); err != nil { | ||||||
|  | 		return fmt.Errorf("find code comments: %v", err) | ||||||
|  | 	} | ||||||
|  | 	for _, comment := range codeComments { | ||||||
|  | 		if err := comment.CheckInvalidation(repo, branch); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // InvalidateCodeComments will lookup the prs for code comments which got invalidated by change | ||||||
|  | func (prs PullRequestList) InvalidateCodeComments(repo *git.Repository, branch string) error { | ||||||
|  | 	return prs.invalidateCodeComments(x, repo, branch) | ||||||
|  | } | ||||||
|  |  | ||||||
| func addHeadRepoTasks(prs []*PullRequest) { | func addHeadRepoTasks(prs []*PullRequest) { | ||||||
| 	for _, pr := range prs { | 	for _, pr := range prs { | ||||||
| 		log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID) | 		log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID) | ||||||
| @@ -1116,10 +1146,29 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if isSync { | 	if isSync { | ||||||
| 		if err = PullRequestList(prs).LoadAttributes(); err != nil { | 		requests := PullRequestList(prs) | ||||||
|  | 		if err = requests.LoadAttributes(); err != nil { | ||||||
| 			log.Error(4, "PullRequestList.LoadAttributes: %v", err) | 			log.Error(4, "PullRequestList.LoadAttributes: %v", err) | ||||||
| 		} | 		} | ||||||
|  | 		var gitRepo *git.Repository | ||||||
|  | 		repo, err := GetRepositoryByID(repoID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error(4, "GetRepositoryByID: %v", err) | ||||||
|  | 			goto REQUIRED_PROCEDURE | ||||||
|  | 		} | ||||||
|  | 		gitRepo, err = git.OpenRepository(repo.RepoPath()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Error(4, "git.OpenRepository: %v", err) | ||||||
|  | 			goto REQUIRED_PROCEDURE | ||||||
|  | 		} | ||||||
|  | 		go func() { | ||||||
|  | 			err := requests.InvalidateCodeComments(gitRepo, branch) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Error(4, "PullRequestList.InvalidateCodeComments: %v", err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  |  | ||||||
|  | 	REQUIRED_PROCEDURE: | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			for _, pr := range prs { | 			for _, pr := range prs { | ||||||
| 				pr.Issue.PullRequest = pr | 				pr.Issue.PullRequest = pr | ||||||
|   | |||||||
| @@ -358,12 +358,11 @@ func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Error | |||||||
|  |  | ||||||
| // CodeCommentForm form for adding code comments for PRs | // CodeCommentForm form for adding code comments for PRs | ||||||
| type CodeCommentForm struct { | type CodeCommentForm struct { | ||||||
| 	Content   string `binding:"Required"` | 	Content  string `binding:"Required"` | ||||||
| 	Side      string `binding:"Required;In(previous,proposed)"` | 	Side     string `binding:"Required;In(previous,proposed)"` | ||||||
| 	Line      int64 | 	Line     int64 | ||||||
| 	TreePath  string `form:"path" binding:"Required"` | 	TreePath string `form:"path" binding:"Required"` | ||||||
| 	CommitSHA string `form:"commit_id" binding:"Required"` | 	IsReview bool   `form:"is_review"` | ||||||
| 	IsReview  bool   `form:"is_review"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Validate validates the fields | // Validate validates the fields | ||||||
|   | |||||||
| @@ -63,14 +63,11 @@ func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	//FIXME check if line, commit and treepath exist | 	//FIXME check if line, commit and treepath exist | ||||||
| 	var err error | 	comment, err := models.CreateCodeComment( | ||||||
| 	comment, err = models.CreateCodeComment( |  | ||||||
| 		ctx.User, | 		ctx.User, | ||||||
| 		issue.Repo, | 		issue.Repo, | ||||||
| 		issue, | 		issue, | ||||||
| 		form.CommitSHA, |  | ||||||
| 		form.Content, | 		form.Content, | ||||||
| 		form.TreePath, | 		form.TreePath, | ||||||
| 		signedLine, | 		signedLine, | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								vendor/code.gitea.io/git/repo_blame.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								vendor/code.gitea.io/git/repo_blame.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,7 +4,21 @@ | |||||||
|  |  | ||||||
| package git | package git | ||||||
|  |  | ||||||
|  | import "fmt" | ||||||
|  |  | ||||||
| // FileBlame return the Blame object of file | // FileBlame return the Blame object of file | ||||||
| func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { | func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { | ||||||
| 	return NewCommand("blame", "--root", file).RunInDirBytes(path) | 	return NewCommand("blame", "--root", file).RunInDirBytes(path) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LineBlame returns the latest commit at the given line | ||||||
|  | func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { | ||||||
|  | 	res, err := NewCommand("blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, file).RunInDir(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if len(res) < 40 { | ||||||
|  | 		return nil, fmt.Errorf("invalid result of blame: %s", res) | ||||||
|  | 	} | ||||||
|  | 	return repo.GetCommit(string(res[:40])) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @@ -3,10 +3,11 @@ | |||||||
| 	"ignore": "test appengine", | 	"ignore": "test appengine", | ||||||
| 	"package": [ | 	"package": [ | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "BfL4Z7P1alyUUNspKJu7Q4GPCNs=", | 			"checksumSHA1": "jkAY8qJRd3N2isGPpoCMoq+QkBc=", | ||||||
|  | 			"origin": "github.com/JonasFranzDEV/git", | ||||||
| 			"path": "code.gitea.io/git", | 			"path": "code.gitea.io/git", | ||||||
| 			"revision": "f1ecc138bebcffed32be1a574ed0c2701b33733f", | 			"revision": "575c3983fb275c7e87906a781ace9d97e8f4071d", | ||||||
| 			"revisionTime": "2018-04-21T01:08:19Z" | 			"revisionTime": "2018-05-13T11:02:42Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", | 			"checksumSHA1": "WMD6+Qh2+5hd9uiq910pF/Ihylw=", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user