mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	The JSONRedirect/JSONOK/JSONError functions were put into "Base" context incorrectly, it would cause abuse. Actually, they are for "web context" only, so, move them to the correct place. And by the way, use them to simplify old code: +75 -196
		
			
				
	
	
		
			296 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package repo
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
 | 
						|
	issues_model "code.gitea.io/gitea/models/issues"
 | 
						|
	pull_model "code.gitea.io/gitea/models/pull"
 | 
						|
	"code.gitea.io/gitea/modules/base"
 | 
						|
	"code.gitea.io/gitea/modules/context"
 | 
						|
	"code.gitea.io/gitea/modules/json"
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
	"code.gitea.io/gitea/modules/setting"
 | 
						|
	"code.gitea.io/gitea/modules/web"
 | 
						|
	"code.gitea.io/gitea/services/forms"
 | 
						|
	pull_service "code.gitea.io/gitea/services/pull"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	tplConversation base.TplName = "repo/diff/conversation"
 | 
						|
	tplNewComment   base.TplName = "repo/diff/new_comment"
 | 
						|
)
 | 
						|
 | 
						|
// RenderNewCodeCommentForm will render the form for creating a new review comment
 | 
						|
func RenderNewCodeCommentForm(ctx *context.Context) {
 | 
						|
	issue := GetActionIssue(ctx)
 | 
						|
	if ctx.Written() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !issue.IsPull {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	currentReview, err := issues_model.GetCurrentReview(ctx, ctx.Doer, issue)
 | 
						|
	if err != nil && !issues_model.IsErrReviewNotExist(err) {
 | 
						|
		ctx.ServerError("GetCurrentReview", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.Data["PageIsPullFiles"] = true
 | 
						|
	ctx.Data["Issue"] = issue
 | 
						|
	ctx.Data["CurrentReview"] = currentReview
 | 
						|
	pullHeadCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(issue.PullRequest.GetGitRefName())
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("GetRefCommitID", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.Data["AfterCommitID"] = pullHeadCommitID
 | 
						|
	ctx.HTML(http.StatusOK, tplNewComment)
 | 
						|
}
 | 
						|
 | 
						|
// CreateCodeComment will create a code comment including an pending review if required
 | 
						|
func CreateCodeComment(ctx *context.Context) {
 | 
						|
	form := web.GetForm(ctx).(*forms.CodeCommentForm)
 | 
						|
	issue := GetActionIssue(ctx)
 | 
						|
	if ctx.Written() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !issue.IsPull {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if ctx.HasError() {
 | 
						|
		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
 | 
						|
		ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	signedLine := form.Line
 | 
						|
	if form.Side == "previous" {
 | 
						|
		signedLine *= -1
 | 
						|
	}
 | 
						|
 | 
						|
	comment, err := pull_service.CreateCodeComment(ctx,
 | 
						|
		ctx.Doer,
 | 
						|
		ctx.Repo.GitRepo,
 | 
						|
		issue,
 | 
						|
		signedLine,
 | 
						|
		form.Content,
 | 
						|
		form.TreePath,
 | 
						|
		!form.SingleReview,
 | 
						|
		form.Reply,
 | 
						|
		form.LatestCommitID,
 | 
						|
	)
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("CreateCodeComment", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if comment == nil {
 | 
						|
		log.Trace("Comment not created: %-v #%d[%d]", ctx.Repo.Repository, issue.Index, issue.ID)
 | 
						|
		ctx.Redirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	log.Trace("Comment created: %-v #%d[%d] Comment[%d]", ctx.Repo.Repository, issue.Index, issue.ID, comment.ID)
 | 
						|
 | 
						|
	if form.Origin == "diff" {
 | 
						|
		renderConversation(ctx, comment)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.Redirect(comment.Link())
 | 
						|
}
 | 
						|
 | 
						|
// UpdateResolveConversation add or remove an Conversation resolved mark
 | 
						|
func UpdateResolveConversation(ctx *context.Context) {
 | 
						|
	origin := ctx.FormString("origin")
 | 
						|
	action := ctx.FormString("action")
 | 
						|
	commentID := ctx.FormInt64("comment_id")
 | 
						|
 | 
						|
	comment, err := issues_model.GetCommentByID(ctx, commentID)
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("GetIssueByID", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if err = comment.LoadIssue(ctx); err != nil {
 | 
						|
		ctx.ServerError("comment.LoadIssue", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if comment.Issue.RepoID != ctx.Repo.Repository.ID {
 | 
						|
		ctx.NotFound("comment's repoID is incorrect", errors.New("comment's repoID is incorrect"))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var permResult bool
 | 
						|
	if permResult, err = issues_model.CanMarkConversation(comment.Issue, ctx.Doer); err != nil {
 | 
						|
		ctx.ServerError("CanMarkConversation", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !permResult {
 | 
						|
		ctx.Error(http.StatusForbidden)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if !comment.Issue.IsPull {
 | 
						|
		ctx.Error(http.StatusBadRequest)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if action == "Resolve" || action == "UnResolve" {
 | 
						|
		err = issues_model.MarkConversation(comment, ctx.Doer, action == "Resolve")
 | 
						|
		if err != nil {
 | 
						|
			ctx.ServerError("MarkConversation", err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		ctx.Error(http.StatusBadRequest)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if origin == "diff" {
 | 
						|
		renderConversation(ctx, comment)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.JSONOK()
 | 
						|
}
 | 
						|
 | 
						|
func renderConversation(ctx *context.Context, comment *issues_model.Comment) {
 | 
						|
	comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, ctx.Data["ShowOutdatedComments"].(bool))
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("FetchCodeCommentsByLine", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.Data["PageIsPullFiles"] = true
 | 
						|
	ctx.Data["comments"] = comments
 | 
						|
	ctx.Data["CanMarkConversation"] = true
 | 
						|
	ctx.Data["Issue"] = comment.Issue
 | 
						|
	if err = comment.Issue.LoadPullRequest(ctx); err != nil {
 | 
						|
		ctx.ServerError("comment.Issue.LoadPullRequest", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	pullHeadCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(comment.Issue.PullRequest.GetGitRefName())
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("GetRefCommitID", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.Data["AfterCommitID"] = pullHeadCommitID
 | 
						|
	ctx.HTML(http.StatusOK, tplConversation)
 | 
						|
}
 | 
						|
 | 
						|
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
 | 
						|
func SubmitReview(ctx *context.Context) {
 | 
						|
	form := web.GetForm(ctx).(*forms.SubmitReviewForm)
 | 
						|
	issue := GetActionIssue(ctx)
 | 
						|
	if ctx.Written() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !issue.IsPull {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if ctx.HasError() {
 | 
						|
		ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
 | 
						|
		ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	reviewType := form.ReviewType()
 | 
						|
	switch reviewType {
 | 
						|
	case issues_model.ReviewTypeUnknown:
 | 
						|
		ctx.ServerError("ReviewType", fmt.Errorf("unknown ReviewType: %s", form.Type))
 | 
						|
		return
 | 
						|
 | 
						|
	// can not approve/reject your own PR
 | 
						|
	case issues_model.ReviewTypeApprove, issues_model.ReviewTypeReject:
 | 
						|
		if issue.IsPoster(ctx.Doer.ID) {
 | 
						|
			var translated string
 | 
						|
			if reviewType == issues_model.ReviewTypeApprove {
 | 
						|
				translated = ctx.Tr("repo.issues.review.self.approval")
 | 
						|
			} else {
 | 
						|
				translated = ctx.Tr("repo.issues.review.self.rejection")
 | 
						|
			}
 | 
						|
 | 
						|
			ctx.Flash.Error(translated)
 | 
						|
			ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var attachments []string
 | 
						|
	if setting.Attachment.Enabled {
 | 
						|
		attachments = form.Files
 | 
						|
	}
 | 
						|
 | 
						|
	_, comm, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, issue, reviewType, form.Content, form.CommitID, attachments)
 | 
						|
	if err != nil {
 | 
						|
		if issues_model.IsContentEmptyErr(err) {
 | 
						|
			ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty"))
 | 
						|
			ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index))
 | 
						|
		} else {
 | 
						|
			ctx.ServerError("SubmitReview", err)
 | 
						|
		}
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d#%s", ctx.Repo.RepoLink, issue.Index, comm.HashTag()))
 | 
						|
}
 | 
						|
 | 
						|
// DismissReview dismissing stale review by repo admin
 | 
						|
func DismissReview(ctx *context.Context) {
 | 
						|
	form := web.GetForm(ctx).(*forms.DismissReviewForm)
 | 
						|
	comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true)
 | 
						|
	if err != nil {
 | 
						|
		ctx.ServerError("pull_service.DismissReview", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	ctx.Redirect(fmt.Sprintf("%s/pulls/%d#%s", ctx.Repo.RepoLink, comm.Issue.Index, comm.HashTag()))
 | 
						|
}
 | 
						|
 | 
						|
// viewedFilesUpdate Struct to parse the body of a request to update the reviewed files of a PR
 | 
						|
// If you want to implement an API to update the review, simply move this struct into modules.
 | 
						|
type viewedFilesUpdate struct {
 | 
						|
	Files         map[string]bool `json:"files"`
 | 
						|
	HeadCommitSHA string          `json:"headCommitSHA"`
 | 
						|
}
 | 
						|
 | 
						|
func UpdateViewedFiles(ctx *context.Context) {
 | 
						|
	// Find corresponding PR
 | 
						|
	issue := checkPullInfo(ctx)
 | 
						|
	if ctx.Written() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	pull := issue.PullRequest
 | 
						|
 | 
						|
	var data *viewedFilesUpdate
 | 
						|
	err := json.NewDecoder(ctx.Req.Body).Decode(&data)
 | 
						|
	if err != nil {
 | 
						|
		log.Warn("Attempted to update a review but could not parse request body: %v", err)
 | 
						|
		ctx.Resp.WriteHeader(http.StatusBadRequest)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Expect the review to have been now if no head commit was supplied
 | 
						|
	if data.HeadCommitSHA == "" {
 | 
						|
		data.HeadCommitSHA = pull.HeadCommitID
 | 
						|
	}
 | 
						|
 | 
						|
	updatedFiles := make(map[string]pull_model.ViewedState, len(data.Files))
 | 
						|
	for file, viewed := range data.Files {
 | 
						|
 | 
						|
		// Only unviewed and viewed are possible, has-changed can not be set from the outside
 | 
						|
		state := pull_model.Unviewed
 | 
						|
		if viewed {
 | 
						|
			state = pull_model.Viewed
 | 
						|
		}
 | 
						|
		updatedFiles[file] = state
 | 
						|
	}
 | 
						|
 | 
						|
	if err := pull_model.UpdateReviewState(ctx, ctx.Doer.ID, pull.ID, data.HeadCommitSHA, updatedFiles); err != nil {
 | 
						|
		ctx.ServerError("UpdateReview", err)
 | 
						|
	}
 | 
						|
}
 |