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(`
  • %s
  • `, index+1, index+1, gotemplate.HTMLEscapeString(line)) + "\n") + } + reviewComment.FileContent = gotemplate.HTML(output.String()) + + output.Reset() + for i := 0; i < len(fileLines); i++ { + output.WriteString(fmt.Sprintf(`%d`, i+int(firstLine)+1, i+int(firstLine)+1)) + } + reviewComment.LineNums = gotemplate.HTML(output.String()) + + reviewComment.HighlightClass = highlight.FileNameToHighlightClass(c.Comment.Location.Path) + } + } + + // + // // Check tag. + // tag, ok = marked[comment.PosterID] + // if ok { + // comment.ShowTag = tag + // continue + // } + // + if ctx.Repo.Repository.IsOwnedBy(reviewComment.Poster.ID) || + (ctx.Repo.Repository.Owner.IsOrganization() && ctx.Repo.Repository.Owner.IsOwnedBy(reviewComment.Poster.ID)) { + reviewComment.ShowTag = models.CommentTagOwner + } else if reviewComment.Poster.IsWriterOfRepo(ctx.Repo.Repository) { + reviewComment.ShowTag = models.CommentTagWriter + } else if reviewComment.Poster.ID == issue.Poster.ID { + reviewComment.ShowTag = models.CommentTagPoster + } + // + // marked[comment.PosterID] = comment.ShowTag + // + isAdded := false + for j := range participants { + if reviewComment.Poster.ID == participants[j].ID { + isAdded = true + break + } + } + if !isAdded && !issue.IsPoster(reviewComment.Poster.ID) { + participants = append(participants, reviewComment.Poster) + } + // } + // + // pull := issue.PullRequest + // canDelete := false + + // if ctx.IsSigned && pull.HeadBranch != "master" { + // + // if err := pull.GetHeadRepo(); err != nil { + // log.Error(4, "GetHeadRepo: %v", err) + // } else if ctx.User.IsWriterOfRepo(pull.HeadRepo) { + // canDelete = true + // deleteBranchURL := pull.HeadRepo.Link() + "/branches/" + pull.HeadBranch + "/delete" + // ctx.Data["DeleteBranchLink"] = fmt.Sprintf("%s?commit=%s&redirect_to=%s", deleteBranchURL, pull.MergedCommitID, ctx.Data["Link"]) + // + // } + issue.Comments = append(issue.Comments, &reviewComment) + } + + // ctx.Data["IsPullBranchDeletable"] = canDelete && git.IsBranchExist(pull.HeadRepo.RepoPath(), pull.HeadBranch) + // + ctx.Data["Participants"] = participants + ctx.Data["NumParticipants"] = len(participants) + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) + ctx.HTML(200, tplIssueView) +} + +// ViewReviewCommits show commits for a pull request +func ViewReviewCommits(ctx *context.Context) { + ctx.Data["PageIsPullCommits"] = true + + repo, _, reviewDetails, _ := prepareReviewInfo(ctx) + if ctx.Written() { + return + } + ctx.Data["Username"] = ctx.Repo.Owner.Name + ctx.Data["Reponame"] = ctx.Repo.Repository.Name + + reviewCommits, err := repo.ListCommitsBetween(reviewDetails.Request.TargetRef, reviewDetails.Request.ReviewRef) + if err != nil { + ctx.Handle(500, "GetReviewCommits", err) + } + + // Reverse list + for i, j := 0, len(reviewCommits)-1; i < j; i, j = i+1, j-1 { + reviewCommits[i], reviewCommits[j] = reviewCommits[j], reviewCommits[i] + } + + commits := list.New() + for _, commitHash := range reviewCommits { + commit, err := ctx.Repo.GitRepo.GetCommit(commitHash) + if err != nil { + ctx.Handle(500, "GetReviewCommit", err) + } + commits.PushBack(commit) + } + + commits = models.ValidateCommitsWithEmails(commits) + ctx.Data["Commits"] = commits + ctx.Data["CommitCount"] = commits.Len() + + ctx.HTML(200, tplPullCommits) +} + +// ViewReviewFiles render pull request changed files list page +func ViewReviewFiles(ctx *context.Context) { + ctx.Data["PageIsPullFiles"] = true + + repo, _, reviewDetails, _ := prepareReviewInfo(ctx) + + if ctx.Written() { + return + } + + 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) + } + + endCommitID := reviewCommits[len(reviewCommits)-1] + diff, err := models.GetDiffRange(ctx.Repo.GitRepo.Path, + startCommitID, endCommitID, setting.Git.MaxGitDiffLines, + setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles) + if err != nil { + ctx.Handle(500, "GetDiffRange", err) + return + } + ctx.Data["Diff"] = diff + ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 + + commit, err := ctx.Repo.GitRepo.GetCommit(endCommitID) + if err != nil { + ctx.Handle(500, "GetCommit", err) + return + } + + headTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) + ctx.Data["Username"] = ctx.Repo.Owner.Name + ctx.Data["Reponame"] = ctx.Repo.Repository.Name + ctx.Data["IsImageFile"] = commit.IsImageFile + ctx.Data["SourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", endCommitID) + ctx.Data["BeforeSourcePath"] = setting.AppSubURL + "/" + path.Join(headTarget, "src", startCommitID) + ctx.Data["RawPath"] = setting.AppSubURL + "/" + path.Join(headTarget, "raw", endCommitID) + ctx.Data["RequireHighlightJS"] = true + + ctx.HTML(200, tplPullFiles) +} diff --git a/routers/repo/setting.go b/routers/repo/setting.go index e29f0fcb1f3cf..ec6772ab6130e 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -152,6 +152,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { repo.ExternalTrackerFormat = form.TrackerURLFormat repo.ExternalTrackerStyle = form.TrackerIssueStyle repo.EnablePulls = form.EnablePulls + repo.EnableReviews = form.EnableReviews if err := models.UpdateRepository(repo, false); err != nil { ctx.Handle(500, "UpdateRepository", err) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 9b94e799a74a6..fe2fd719e1472 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -62,6 +62,11 @@ {{.i18n.Tr "repo.pulls"}} {{.Repository.NumOpenPulls}} {{end}} + {{if .Repository.AllowsReviews}} + + {{.i18n.Tr "repo.reviews"}} {{.Repository.NumOpenReviews}} + + {{end}} {{.i18n.Tr "repo.commits"}} {{.CommitsCount}} diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index d00c9aea21144..83fcaf30d66af 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -101,8 +101,16 @@ {{range .Issues}} {{ $timeStr:= TimeSince .Created $.Lang }}
  • -
    #{{.Index}}
    - {{.Title}} + {{if .IsReview}} + {{.Title}} +
    +
    {{.GetIndex}}
    + {{end}} + + {{if not .IsReview}} +
    #{{.GetIndex}}
    + {{.Title}} + {{end}} {{range .Labels}} {{.Name}} diff --git a/templates/repo/issue/view.tmpl b/templates/repo/issue/view.tmpl index 2260f17399d9b..e0caae2243ccb 100644 --- a/templates/repo/issue/view.tmpl +++ b/templates/repo/issue/view.tmpl @@ -13,7 +13,7 @@
    - {{if .Issue.IsPull}} + {{if or .Issue.IsPull .Issue.IsReview}} {{template "repo/issue/view_title" .}} {{template "repo/pulls/tab_menu" .}}
    diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 8c1f266f1c689..1089ba3af6a40 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -4,7 +4,7 @@ {{template "base/alert" .}}
    {{end}} - {{if not .Issue.IsPull}} + {{if not (or .Issue.IsPull .Issue.IsReview)}} {{template "repo/issue/view_title" .}} {{end}} @@ -35,7 +35,7 @@ {{end}}
    {{.Issue.Content}}
    -
    +
    {{if .Issue.Attachments}}
    @@ -88,6 +88,18 @@
    +
    +
    + + + + + + + +
    {{.LineNums}}
      {{.FileContent}}
    +
    +
    {{if .RenderedContent}} {{.RenderedContent|Str2html}} @@ -220,7 +232,7 @@
    -
    + {{template "repo/issue/comment_tab" .}} {{.CsrfTokenHtml}} @@ -258,7 +270,7 @@ {{.i18n.Tr "repo.issues.new.labels"}} -