gogs/routers/repo/view.go
Unknwon ebc0943713
templates/repo: only show Git stats in repository home page (#3518)
Move 'Commits' and 'Releases' tabs down to body.

This patch also reduces page load time for pages that do not need
to use commits count anywhere. Get commits count can hurt
performance badly for huge repositories that has tens of thousands
commits like Linux Kernel.
2017-03-10 15:13:48 -05:00

351 lines
9.4 KiB
Go

// Copyright 2014 The Gogs 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 repo
import (
"bytes"
"fmt"
gotemplate "html/template"
"io/ioutil"
"path"
"strings"
"github.com/Unknwon/paginater"
log "gopkg.in/clog.v1"
"github.com/gogits/git-module"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/context"
"github.com/gogits/gogs/modules/markdown"
"github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/modules/template"
"github.com/gogits/gogs/modules/template/highlight"
)
const (
HOME base.TplName = "repo/home"
WATCHERS base.TplName = "repo/watchers"
FORKS base.TplName = "repo/forks"
)
func renderDirectory(ctx *context.Context, treeLink string) {
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
if err != nil {
ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
return
}
entries, err := tree.ListEntries()
if err != nil {
ctx.Handle(500, "ListEntries", err)
return
}
entries.Sort()
ctx.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(ctx.Repo.Commit, ctx.Repo.TreePath, setting.Repository.CommitsFetchConcurrency)
if err != nil {
ctx.Handle(500, "GetCommitsInfo", err)
return
}
var readmeFile *git.Blob
for _, entry := range entries {
if entry.IsDir() || !markdown.IsReadmeFile(entry.Name()) {
continue
}
// TODO: collect all possible README files and show with priority.
readmeFile = entry.Blob()
break
}
if readmeFile != nil {
ctx.Data["RawFileLink"] = ""
ctx.Data["ReadmeInList"] = true
ctx.Data["ReadmeExist"] = true
dataRc, err := readmeFile.Data()
if err != nil {
ctx.Handle(500, "Data", err)
return
}
buf := make([]byte, 1024)
n, _ := dataRc.Read(buf)
buf = buf[:n]
isTextFile := base.IsTextFile(buf)
ctx.Data["IsTextFile"] = isTextFile
ctx.Data["FileName"] = readmeFile.Name()
if isTextFile {
d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...)
switch {
case markdown.IsMarkdownFile(readmeFile.Name()):
ctx.Data["IsMarkdown"] = true
buf = markdown.Render(buf, treeLink, ctx.Repo.Repository.ComposeMetas())
default:
buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
}
ctx.Data["FileContent"] = string(buf)
}
}
// Show latest commit info of repository in table header,
// or of directory if not in root directory.
latestCommit := ctx.Repo.Commit
if len(ctx.Repo.TreePath) > 0 {
latestCommit, err = ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
if err != nil {
ctx.Handle(500, "GetCommitByPath", err)
return
}
}
ctx.Data["LatestCommit"] = latestCommit
ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
if ctx.Repo.CanEnableEditor() {
ctx.Data["CanAddFile"] = true
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled
}
}
func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
ctx.Data["IsViewFile"] = true
blob := entry.Blob()
dataRc, err := blob.Data()
if err != nil {
ctx.Handle(500, "Data", err)
return
}
ctx.Data["FileSize"] = blob.Size()
ctx.Data["FileName"] = blob.Name()
ctx.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())
ctx.Data["RawFileLink"] = rawLink + "/" + ctx.Repo.TreePath
buf := make([]byte, 1024)
n, _ := dataRc.Read(buf)
buf = buf[:n]
isTextFile := base.IsTextFile(buf)
ctx.Data["IsTextFile"] = isTextFile
// Assume file is not editable first.
if !isTextFile {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
}
canEnableEditor := ctx.Repo.CanEnableEditor()
switch {
case isTextFile:
if blob.Size() >= setting.UI.MaxDisplayFileSize {
ctx.Data["IsFileTooLarge"] = true
break
}
d, _ := ioutil.ReadAll(dataRc)
buf = append(buf, d...)
isMarkdown := markdown.IsMarkdownFile(blob.Name())
ctx.Data["IsMarkdown"] = isMarkdown
ctx.Data["ReadmeExist"] = isMarkdown && markdown.IsReadmeFile(blob.Name())
ctx.Data["IsIPythonNotebook"] = strings.HasSuffix(blob.Name(), ".ipynb")
if isMarkdown {
ctx.Data["FileContent"] = string(markdown.Render(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
} else {
// Building code view blocks with line number on server side.
var fileContent string
if err, content := template.ToUTF8WithErr(buf); err != nil {
if err != nil {
log.Error(4, "ToUTF8WithErr: %s", err)
}
fileContent = string(buf)
} else {
fileContent = content
}
var output bytes.Buffer
lines := strings.Split(fileContent, "\n")
for index, line := range lines {
output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, gotemplate.HTMLEscapeString(line)) + "\n")
}
ctx.Data["FileContent"] = gotemplate.HTML(output.String())
output.Reset()
for i := 0; i < len(lines); i++ {
output.WriteString(fmt.Sprintf(`<span id="L%d">%d</span>`, i+1, i+1))
}
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
}
if canEnableEditor {
ctx.Data["CanEditFile"] = true
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
} else if !ctx.Repo.IsViewBranch {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
} else if !ctx.Repo.IsWriter() {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
}
case base.IsPDFFile(buf):
ctx.Data["IsPDFFile"] = true
case base.IsVideoFile(buf):
ctx.Data["IsVideoFile"] = true
case base.IsImageFile(buf):
ctx.Data["IsImageFile"] = true
}
if canEnableEditor {
ctx.Data["CanDeleteFile"] = true
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
} else if !ctx.Repo.IsViewBranch {
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
} else if !ctx.Repo.IsWriter() {
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
}
}
func setEditorconfigIfExists(ctx *context.Context) {
ec, err := ctx.Repo.GetEditorconfig()
if err != nil && !git.IsErrNotExist(err) {
log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", ctx.Repo.Repository.ID, err)
return
}
ctx.Data["Editorconfig"] = ec
}
func Home(ctx *context.Context) {
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
if len(ctx.Repo.Repository.Description) > 0 {
title += ": " + ctx.Repo.Repository.Description
}
ctx.Data["Title"] = title
if ctx.Repo.BranchName != ctx.Repo.Repository.DefaultBranch {
ctx.Data["Title"] = title + " @ " + ctx.Repo.BranchName
}
ctx.Data["PageIsViewCode"] = true
ctx.Data["RequireHighlightJS"] = true
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchName
treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchName
isRootDir := false
if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + ctx.Repo.TreePath
} else {
isRootDir = true
// Only show Git stats panel when view root directory
var err error
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "CommitsCount", err)
return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
}
ctx.Data["PageIsRepoHome"] = isRootDir
// Get current entry user currently looking at.
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil {
ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
return
}
if entry.IsDir() {
renderDirectory(ctx, treeLink)
} else {
renderFile(ctx, entry, treeLink, rawLink)
}
if ctx.Written() {
return
}
setEditorconfigIfExists(ctx)
if ctx.Written() {
return
}
var treeNames []string
paths := make([]string, 0, 5)
if len(ctx.Repo.TreePath) > 0 {
treeNames = strings.Split(ctx.Repo.TreePath, "/")
for i := range treeNames {
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
}
ctx.Data["HasParentPath"] = true
if len(paths)-2 >= 0 {
ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
}
}
ctx.Data["Paths"] = paths
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink
ctx.HTML(200, HOME)
}
func RenderUserCards(ctx *context.Context, total int, getter func(page int) ([]*models.User, error), tpl base.TplName) {
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}
pager := paginater.New(total, models.ItemsPerPage, page, 5)
ctx.Data["Page"] = pager
items, err := getter(pager.Current())
if err != nil {
ctx.Handle(500, "getter", err)
return
}
ctx.Data["Cards"] = items
ctx.HTML(200, tpl)
}
func Watchers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.watchers")
ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers")
ctx.Data["PageIsWatchers"] = true
RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers, WATCHERS)
}
func Stars(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.stargazers")
ctx.Data["CardsTitle"] = ctx.Tr("repo.stargazers")
ctx.Data["PageIsStargazers"] = true
RenderUserCards(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers, WATCHERS)
}
func Forks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repos.forks")
forks, err := ctx.Repo.Repository.GetForks()
if err != nil {
ctx.Handle(500, "GetForks", err)
return
}
for _, fork := range forks {
if err = fork.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", err)
return
}
}
ctx.Data["Forks"] = forks
ctx.HTML(200, FORKS)
}