diff --git a/cmd/web.go b/cmd/web.go
index 9942f19a50c7c..01ca51a00a41e 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -536,6 +536,8 @@ func runWeb(ctx *cli.Context) error {
m.Get("/releases", repo.Releases)
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
+ m.Get("/reviews", repo.RetrieveLabels, repo.Reviews)
+ m.Get("/reviews/:index", context.RepoRef(), repo.SetEditorconfigIfExists, repo.ViewReview)
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
m.Get("/milestones", repo.Milestones)
}, context.RepoRef())
@@ -564,6 +566,12 @@ func runWeb(ctx *cli.Context) error {
m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
}, repo.MustAllowPulls)
+ m.Group("/reviews/:index", func() {
+ m.Get("/commits", context.RepoRef(), repo.ViewReviewCommits)
+ m.Get("/files", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ViewReviewFiles)
+ m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
+ }, repo.MustAllowReviews)
+
m.Group("", func() {
m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
m.Get("/raw/*", repo.SingleDownload)
diff --git a/models/issue.go b/models/issue.go
index ac50d2dfba313..4fe642d13e5ae 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -7,6 +7,7 @@ package models
import (
"errors"
"fmt"
+ "strconv"
"strings"
"time"
@@ -29,6 +30,7 @@ type Issue struct {
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
+ Revision string `xorm:"-"` // revision for appraise requests
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
Title string `xorm:"name"`
@@ -43,6 +45,7 @@ type Issue struct {
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
+ IsReview bool `xorm:"-"` // Indicates whether is a review or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
@@ -236,6 +239,14 @@ func (issue *Issue) APIFormat() *api.Issue {
return apiIssue
}
+// GetIndex returns string index for templates
+func (issue *Issue) GetIndex() string {
+ if issue.IsReview {
+ return issue.Revision
+ }
+ return strconv.Itoa(int(issue.Index))
+}
+
// HashTag returns unique hash tag for issue.
func (issue *Issue) HashTag() string {
return "issue-" + com.ToStr(issue.ID)
@@ -1734,4 +1745,3 @@ func DeleteMilestoneByRepoID(repoID, id int64) error {
}
return sess.Commit()
}
-
diff --git a/models/issue_comment.go b/models/issue_comment.go
index e9a401b864299..00b8c58242ee5 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -6,6 +6,7 @@ package models
import (
"fmt"
+ gotemplate "html/template"
"strings"
"time"
@@ -61,6 +62,10 @@ type Comment struct {
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
+ FileContent gotemplate.HTML `xorm:"-"`
+ LineNums gotemplate.HTML `xorm:"-"`
+ HighlightClass string `xorm:"-"`
+
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"`
Updated time.Time `xorm:"-"`
diff --git a/models/repo.go b/models/repo.go
index c2480d9ddbf58..467768e22a8e1 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -189,6 +189,7 @@ type Repository struct {
NumPulls int
NumClosedPulls int
NumOpenPulls int `xorm:"-"`
+ NumOpenReviews int `xorm:"-"`
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
NumOpenMilestones int `xorm:"-"`
@@ -211,6 +212,7 @@ type Repository struct {
ExternalTrackerStyle string
ExternalMetas map[string]string `xorm:"-"`
EnablePulls bool `xorm:"NOT NULL DEFAULT true"`
+ EnableReviews bool `xorm:"NOT NULL DEFAULT true"`
IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
ForkID int64 `xorm:"INDEX"`
@@ -485,6 +487,11 @@ func (repo *Repository) AllowsPulls() bool {
return repo.CanEnablePulls() && repo.EnablePulls
}
+// AllowsReviews returns true if repository meets the requirements of accepting pulls and has them enabled.
+func (repo *Repository) AllowsReviews() bool {
+ return repo.EnableReviews
+}
+
// CanEnableEditor returns true if repository meets the requirements of web editor.
func (repo *Repository) CanEnableEditor() bool {
return !repo.IsMirror
@@ -1035,15 +1042,16 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
}
repo := &Repository{
- OwnerID: u.ID,
- Owner: u,
- Name: opts.Name,
- LowerName: strings.ToLower(opts.Name),
- Description: opts.Description,
- IsPrivate: opts.IsPrivate,
- EnableWiki: true,
- EnableIssues: true,
- EnablePulls: true,
+ OwnerID: u.ID,
+ Owner: u,
+ Name: opts.Name,
+ LowerName: strings.ToLower(opts.Name),
+ Description: opts.Description,
+ IsPrivate: opts.IsPrivate,
+ EnableWiki: true,
+ EnableIssues: true,
+ EnablePulls: true,
+ EnableReviews: true,
}
sess := x.NewSession()
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index dda1f9d252617..119b232c0d77d 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -104,6 +104,7 @@ type RepoSettingForm struct {
TrackerURLFormat string
TrackerIssueStyle string
EnablePulls bool
+ EnableReviews bool
}
// Validate valideates the fields
@@ -267,7 +268,7 @@ type NewReleaseForm struct {
Content string
Draft string
Prerelease bool
- Files []string
+ Files []string
}
// Validate valideates the fields
@@ -281,7 +282,7 @@ type EditReleaseForm struct {
Content string `form:"content"`
Draft string `form:"draft"`
Prerelease bool `form:"prerelease"`
- Files []string
+ Files []string
}
// Validate valideates the fields
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 338a78cb93cea..2acbdf7b877ca 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -15,6 +15,8 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
+ "github.com/google/git-appraise/repository"
+ "github.com/google/git-appraise/review"
editorconfig "gopkg.in/editorconfig/editorconfig-core-go.v1"
macaron "gopkg.in/macaron.v1"
)
@@ -286,6 +288,15 @@ func RepoAssignment(args ...bool) macaron.Handler {
ctx.Data["Branches"] = brs
ctx.Data["BrancheCount"] = len(brs)
+ // Count open reviews
+ if repo.AllowsReviews() {
+ appraiseRepo, err := repository.NewGitRepo(ctx.Repo.GitRepo.Path)
+ if err != nil {
+ ctx.Handle(500, "OpenGitRepository", err)
+ }
+ ctx.Repo.Repository.NumOpenReviews = len(review.ListOpen(appraiseRepo))
+ }
+
// If not branch selected, try default one.
// If default branch doesn't exists, fall back to some other branch.
if len(ctx.Repo.BranchName) == 0 {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9b9e25ba812c3..3d875e7f6e90e 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -459,6 +459,7 @@ branches = Branches
tags = Tags
issues = Issues
pulls = Pull Requests
+reviews = Reviews
labels = Labels
milestones = Milestones
commits = Commits
@@ -708,6 +709,7 @@ settings.tracker_issue_style.numeric = Numeric
settings.tracker_issue_style.alphanumeric = Alphanumeric
settings.tracker_url_format_desc = You can use placeholder {user} {repo} {index}
for user name, repository name and issue index.
settings.pulls_desc = Enable pull requests to accept public contributions
+settings.reviews_desc = Enable appraise reviews
settings.danger_zone = Danger Zone
settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name.
settings.convert = Convert To Regular Repository
diff --git a/public/css/index.css b/public/css/index.css
index 7c84cf8517aa9..11e70f9efa6f5 100644
--- a/public/css/index.css
+++ b/public/css/index.css
@@ -1315,64 +1315,64 @@ footer .ui.language .menu {
.repository.file.list #file-content .view-raw img {
padding: 5px 5px 0 5px;
}
-.repository.file.list #file-content .code-view * {
+.code-view * {
font-size: 12px;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
line-height: 20px;
}
-.repository.file.list #file-content .code-view table {
+.code-view table {
width: 100%;
}
-.repository.file.list #file-content .code-view .lines-num {
+.code-view .lines-num {
vertical-align: top;
text-align: right;
color: #999;
background: #f5f5f5;
width: 1%;
}
-.repository.file.list #file-content .code-view .lines-num span {
+.code-view .lines-num span {
line-height: 20px;
padding: 0 10px;
cursor: pointer;
display: block;
}
-.repository.file.list #file-content .code-view .lines-num,
-.repository.file.list #file-content .code-view .lines-code {
+.code-view .lines-num,
+.code-view .lines-code {
padding: 0;
}
-.repository.file.list #file-content .code-view .lines-num pre,
-.repository.file.list #file-content .code-view .lines-code pre,
-.repository.file.list #file-content .code-view .lines-num ol,
-.repository.file.list #file-content .code-view .lines-code ol,
-.repository.file.list #file-content .code-view .lines-num .hljs,
-.repository.file.list #file-content .code-view .lines-code .hljs {
+.code-view .lines-num pre,
+.code-view .lines-code pre,
+.code-view .lines-num ol,
+.code-view .lines-code ol,
+.code-view .lines-num .hljs,
+.code-view .lines-code .hljs {
background-color: white;
margin: 0;
padding: 0 !important;
}
-.repository.file.list #file-content .code-view .lines-num pre li,
-.repository.file.list #file-content .code-view .lines-code pre li,
-.repository.file.list #file-content .code-view .lines-num ol li,
-.repository.file.list #file-content .code-view .lines-code ol li,
-.repository.file.list #file-content .code-view .lines-num .hljs li,
-.repository.file.list #file-content .code-view .lines-code .hljs li {
+.code-view .lines-num pre li,
+.code-view .lines-code pre li,
+.code-view .lines-num ol li,
+.code-view .lines-code ol li,
+.code-view .lines-num .hljs li,
+.code-view .lines-code .hljs li {
display: inline-block;
width: 100%;
}
-.repository.file.list #file-content .code-view .lines-num pre li.active,
-.repository.file.list #file-content .code-view .lines-code pre li.active,
-.repository.file.list #file-content .code-view .lines-num ol li.active,
-.repository.file.list #file-content .code-view .lines-code ol li.active,
-.repository.file.list #file-content .code-view .lines-num .hljs li.active,
-.repository.file.list #file-content .code-view .lines-code .hljs li.active {
+.code-view .lines-num pre li.active,
+.code-view .lines-code pre li.active,
+.code-view .lines-num ol li.active,
+.code-view .lines-code ol li.active,
+.code-view .lines-num .hljs li.active,
+.code-view .lines-code .hljs li.active {
background: #ffffdd;
}
-.repository.file.list #file-content .code-view .lines-num pre li:before,
-.repository.file.list #file-content .code-view .lines-code pre li:before,
-.repository.file.list #file-content .code-view .lines-num ol li:before,
-.repository.file.list #file-content .code-view .lines-code ol li:before,
-.repository.file.list #file-content .code-view .lines-num .hljs li:before,
-.repository.file.list #file-content .code-view .lines-code .hljs li:before {
+.code-view .lines-num pre li:before,
+.code-view .lines-code pre li:before,
+.code-view .lines-num ol li:before,
+.code-view .lines-code ol li:before,
+.code-view .lines-num .hljs li:before,
+.code-view .lines-code .hljs li:before {
content: ' ';
}
.repository.file.list .sidebar {
@@ -1514,6 +1514,11 @@ footer .ui.language .menu {
font-size: 2.3rem;
margin-bottom: 5px;
}
+.repository.view.issue .title .revision {
+ top: -1rem;
+ padding-top: 0;
+ padding-bottom: 5px;
+}
.repository.view.issue .title h1 .ui.input {
font-size: 0.5em;
vertical-align: top;
@@ -1531,6 +1536,7 @@ footer .ui.language .menu {
}
.repository.view.issue .title .label {
margin-right: 10px;
+ margin-left: 0px;
}
.repository.view.issue .title .edit-zone {
margin-top: 10px;
@@ -2265,6 +2271,13 @@ footer .ui.language .menu {
font-weight: bold;
margin: 0 6px;
}
+.issue.list > .item .title.review {
+ margin: 0 0;
+}
+.issue.list > .item .revision {
+ font-size: 12px;
+ color: #aaa;
+}
.issue.list > .item .title:hover {
color: #000;
}
diff --git a/public/less/_repository.less b/public/less/_repository.less
index 0babcedf395ee..ad88684867146 100644
--- a/public/less/_repository.less
+++ b/public/less/_repository.less
@@ -253,53 +253,6 @@
padding: 5px 5px 0 5px;
}
}
-
- .code-view {
- * {
- font-size: 12px;
- font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
- line-height: 20px;
- }
-
- table {
- width: 100%;
- }
- .lines-num {
- vertical-align: top;
- text-align: right;
- color: #999;
- background: #f5f5f5;
- width: 1%;
-
- span {
- line-height: 20px;
- padding: 0 10px;
- cursor: pointer;
- display: block;
- }
- }
- .lines-num,
- .lines-code {
- padding: 0;
- pre,
- ol,
- .hljs {
- background-color: white;
- margin: 0;
- padding: 0 !important;
- li {
- display: inline-block;
- width: 100%;
- &.active {
- background: #ffffdd;
- }
- &:before {
- content: ' ';
- }
- }
- }
- }
- }
}
.sidebar {
@@ -1483,3 +1436,50 @@
}
.generate-tab-size(@n, (@i + 1));
}
+
+.code-view {
+ * {
+ font-size: 12px;
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ line-height: 20px;
+ }
+
+ table {
+ width: 100%;
+ }
+ .lines-num {
+ vertical-align: top;
+ text-align: right;
+ color: #999;
+ background: #f5f5f5;
+ width: 1%;
+
+ span {
+ line-height: 20px;
+ padding: 0 10px;
+ cursor: pointer;
+ display: block;
+ }
+ }
+ .lines-num,
+ .lines-code {
+ padding: 0;
+ pre,
+ ol,
+ .hljs {
+ background-color: white;
+ margin: 0;
+ padding: 0 !important;
+ li {
+ display: inline-block;
+ width: 100%;
+ &.active {
+ background: #ffffdd;
+ }
+ &:before {
+ content: ' ';
+ }
+ }
+ }
+ }
+}
diff --git a/routers/init.go b/routers/init.go
index 697f33835cde1..b58467e4832c9 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -24,7 +24,7 @@ func checkRunMode() {
switch setting.Cfg.Section("").Key("RUN_MODE").String() {
case "prod":
macaron.Env = macaron.PROD
- macaron.ColorLog = false
+ macaron.ColorLog = true
setting.ProdMode = true
default:
git.Debug = true
diff --git a/routers/repo/reviews.go b/routers/repo/reviews.go
new file mode 100644
index 0000000000000..a5bd2961e9b17
--- /dev/null
+++ b/routers/repo/reviews.go
@@ -0,0 +1,481 @@
+// Copyright 2017 The Gitea. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "bytes"
+ "container/list"
+ "fmt"
+ gotemplate "html/template"
+ "log"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/highlight"
+ "code.gitea.io/gitea/modules/markdown"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/Unknwon/com"
+ "github.com/google/git-appraise/repository"
+ "github.com/google/git-appraise/review"
+)
+
+const (
+ tplReviews base.TplName = "repo/review/list"
+)
+
+// MustAllowReviews check if repository enable reviews
+func MustAllowReviews(ctx *context.Context) {
+ if !ctx.Repo.Repository.AllowsReviews() {
+ ctx.Handle(404, "MustAllowReviews", nil)
+ return
+ }
+}
+
+// Reviews render issues page
+func Reviews(ctx *context.Context) {
+ repo, err := repository.NewGitRepo(ctx.Repo.GitRepo.Path)
+ if err != nil {
+ ctx.Handle(500, "OpenGitRepository", err)
+ }
+ total := review.ListAll(repo)
+
+ viewType := ctx.Query("type")
+ sortType := ctx.Query("sort")
+ selectLabels := ctx.Query("labels")
+ milestoneID := ctx.QueryInt64("milestone")
+ assigneeID := ctx.QueryInt64("assignee")
+ isShowClosed := ctx.Query("state") == "closed"
+
+ issues := []*models.Issue{}
+ stat := models.IssueStats{OpenCount: 0, ClosedCount: 0, AllCount: 0}
+
+ for _, review := range total {
+ details, _ := review.Details()
+ notes, _ := details.GetAnalysesNotes()
+ for _, note := range notes {
+ log.Print(note)
+ }
+
+ timestamp, _ := strconv.Atoi(review.Request.Timestamp)
+ time := time.Unix(int64(timestamp), 0)
+
+ closed := false
+ if review.Submitted || review.Request.TargetRef == "" {
+ closed = true
+ stat.ClosedCount++
+ } else {
+ stat.OpenCount++
+ }
+ readed := false
+ if (review.Resolved != nil && *review.Resolved) || review.Request.TargetRef == "" {
+ readed = true
+ }
+
+ user, err := models.GetUserByEmail(review.Request.Requester)
+ if err != nil {
+ user = &models.User{Name: review.Request.Requester}
+ }
+
+ issue := models.Issue{
+ Revision: review.Revision,
+ Title: strings.Split(review.Request.Description, "\n\n")[0],
+ Poster: user,
+ Created: time,
+ CreatedUnix: int64(timestamp),
+ Labels: []*models.Label{},
+ IsClosed: closed,
+ IsRead: readed,
+ IsReview: true,
+ }
+
+ if closed && isShowClosed {
+ issues = append(issues, &issue)
+ }
+ if !closed && !isShowClosed {
+ issues = append(issues, &issue)
+ }
+ stat.AllCount++
+ }
+
+ ctx.Data["Title"] = ctx.Tr("repo.reviews")
+ ctx.Data["PageIsReviewList"] = true
+ // ctx.Data["Page"] = 0
+ ctx.Data["Issues"] = issues
+ ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID)
+ if err != nil {
+ ctx.Handle(500, "GetAllRepoMilestones", err)
+ return
+ }
+
+ // Get assignees.
+ ctx.Data["Assignees"], err = ctx.Repo.Repository.GetAssignees()
+ if err != nil {
+ ctx.Handle(500, "GetAssignees", err)
+ return
+ }
+ ctx.Data["SelectLabels"] = com.StrTo(selectLabels).MustInt64()
+ ctx.Data["IssueStats"] = stat
+ ctx.Data["ViewType"] = viewType
+ ctx.Data["SortType"] = sortType
+ ctx.Data["MilestoneID"] = milestoneID
+ ctx.Data["AssigneeID"] = assigneeID
+ ctx.Data["IsShowClosed"] = isShowClosed
+ if isShowClosed {
+ ctx.Data["State"] = "closed"
+ } else {
+ ctx.Data["State"] = "open"
+ }
+ ctx.HTML(200, tplIssues)
+}
+
+func prepareReviewInfo(ctx *context.Context) (*repository.GitRepo, *review.Summary, *review.Review, *models.Issue) {
+ ctx.Data["PageIsReviewList"] = true
+
+ repo, err := repository.NewGitRepo(ctx.Repo.GitRepo.Path)
+ if err != nil {
+ ctx.Handle(500, "OpenGitRepository", err)
+ }
+ reviewSummary, err := review.GetSummary(repo, ctx.Params(":index"))
+ if err != nil {
+ ctx.Handle(500, "GetReviewSummary", err)
+ }
+ reviewDetails, err := reviewSummary.Details()
+ if err != nil {
+ ctx.Handle(500, "GetReview", err)
+ }
+
+ issueUser, _ := models.GetUserByEmail(reviewDetails.Request.Requester)
+ issueCreatedTimestamp, _ := strconv.Atoi(reviewDetails.Request.Timestamp)
+ issueCreated := time.Unix(int64(issueCreatedTimestamp), 0)
+
+ closed := false
+ if reviewDetails.Submitted || reviewDetails.Request.TargetRef == "" {
+ closed = true
+ }
+ readed := false
+ if (reviewDetails.Resolved != nil && *reviewDetails.Resolved) || reviewDetails.Request.TargetRef == "" {
+ readed = true
+ }
+
+ issue := &models.Issue{
+ ID: 0,
+ Index: 0,
+ Revision: reviewDetails.Revision,
+ Title: strings.Split(reviewDetails.Request.Description, "\n\n")[0],
+ Content: reviewDetails.Request.Description,
+ Poster: issueUser,
+ Created: issueCreated,
+ Labels: []*models.Label{},
+ IsClosed: closed,
+ IsRead: readed,
+ IsReview: true,
+ NumComments: len(reviewDetails.Comments),
+ Comments: []*models.Comment{},
+ PullRequest: &models.PullRequest{
+ HasMerged: reviewDetails.Submitted,
+ },
+ }
+
+ issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
+ ctx.Repo.Repository.ComposeMetas()))
+
+ ctx.Data["HasMerged"] = reviewDetails.Submitted
+
+ reviewCommits, err := repo.ListCommitsBetween(reviewDetails.Request.TargetRef, reviewDetails.Request.ReviewRef)
+ if err != nil {
+ ctx.Handle(500, "ListCommitsBetween", err)
+ }
+
+ startCommitID, err := reviewDetails.GetBaseCommit()
+ if err != nil {
+ ctx.Handle(500, "GetBaseCommit", err)
+ }
+
+ ctx.Data["HeadTarget"] = reviewDetails.Request.ReviewRef
+ ctx.Data["BaseTarget"] = reviewDetails.Request.TargetRef
+ ctx.Data["Title"] = strings.Split(reviewDetails.Request.Description, "\n\n")[0]
+ ctx.Data["Issue"] = issue
+ ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))
+
+ ctx.Data["NumCommits"], err = ctx.Repo.GitRepo.CommitsCountBetween(startCommitID, reviewCommits[len(reviewCommits)-1])
+ if err != nil {
+ ctx.Handle(500, "Repo.GitRepo.CommitsCountBetween", err)
+ }
+ ctx.Data["NumFiles"], err = ctx.Repo.GitRepo.FilesCountBetween(startCommitID, reviewCommits[len(reviewCommits)-1])
+ if err != nil {
+ ctx.Handle(500, "Repo.GitRepo.FilesCountBetween", err)
+ }
+
+ return repo, reviewSummary, reviewDetails, issue
+}
+
+// ViewReview render issue view page
+func ViewReview(ctx *context.Context) {
+ ctx.Data["RequireHighlightJS"] = true
+ ctx.Data["RequireDropzone"] = true
+
+ repo, _, reviewDetails, issue := prepareReviewInfo(ctx)
+
+ MustAllowReviews(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ ctx.Data["PageIsPullConversation"] = true
+ //
+ // repo := ctx.Repo.Repository
+ //
+ // if issue.PullRequest.HasMerged {
+ // ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
+ // PrepareMergedViewPullInfo(ctx, issue)
+ // } else {
+ // PrepareViewPullInfo(ctx, issue)
+ // }
+ // if ctx.Written() {
+ // return
+ // }
+
+ // // Metas.
+ // // Check labels.
+ // labelIDMark := make(map[int64]bool)
+ // for i := range issue.Labels {
+ // labelIDMark[issue.Labels[i].ID] = true
+ // }
+ // labels, err := models.GetLabelsByRepoID(repo.ID, "")
+ // if err != nil {
+ // ctx.Handle(500, "GetLabelsByRepoID", err)
+ // return
+ // }
+ // hasSelected := false
+ // for i := range labels {
+ // if labelIDMark[labels[i].ID] {
+ // labels[i].IsChecked = true
+ // hasSelected = true
+ // }
+ // }
+ // ctx.Data["HasSelectedLabel"] = hasSelected
+ // ctx.Data["Labels"] = labels
+ //
+ // // Check milestone and assignee.
+ // if ctx.Repo.IsWriter() {
+ // RetrieveRepoMilestonesAndAssignees(ctx, repo)
+ // if ctx.Written() {
+ // return
+ // }
+ // }
+
+ // if ctx.IsSigned {
+ // // Update issue-user.
+ // if err = issue.ReadBy(ctx.User.ID); err != nil {
+ // ctx.Handle(500, "ReadBy", err)
+ // return
+ // }
+ // }
+ //
+ // var (
+ // tag models.CommentTag
+ // ok bool
+ // marked = make(map[int64]models.CommentTag)
+ // var comment *models.Comment
+ participants := make([]*models.User, 1, 10)
+ // )
+ //
+ // // Render comments and and fetch participants.
+ participants[0] = issue.Poster
+ for _, c := range reviewDetails.Comments {
+ log.Print(c.Comment, c.Hash)
+ reviewComment := models.Comment{}
+
+ commentUser, _ := models.GetUserByEmail(c.Comment.Author)
+ reviewComment.Poster = commentUser
+
+ reviewComment.Content = c.Comment.Description
+
+ reviewComment.RenderedContent = string(markdown.Render([]byte(reviewComment.Content), ctx.Repo.RepoLink,
+ ctx.Repo.Repository.ComposeMetas()))
+
+ commentCreatedTimestamp, _ := strconv.Atoi(c.Comment.Timestamp)
+ commentCreated := time.Unix(int64(commentCreatedTimestamp), 0)
+ reviewComment.Created = commentCreated
+ // c.Comment.Location.Path
+ // c.Comment.Location.Commit
+ if c.Comment.Location != nil && c.Comment.Location.Range != nil {
+ reviewComment.Line = int64(c.Comment.Location.Range.StartLine)
+
+ contents, err := repo.Show(c.Comment.Location.Commit, c.Comment.Location.Path)
+ if err != nil {
+ continue
+ }
+ lines := strings.Split(contents, "\n")
+ if c.Comment.Location.Range.StartLine <= uint32(len(lines)) {
+ var firstLine uint32
+ lastLine := c.Comment.Location.Range.StartLine
+ if lastLine > 5 {
+ firstLine = lastLine - 5
+ }
+
+ var output bytes.Buffer
+ fileLines := lines[firstLine:lastLine]
+ for index, line := range fileLines {
+ output.WriteString(fmt.Sprintf(`
{{.LineNums}} | +
|
+