mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
11 KiB
470 lines
11 KiB
// 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" |
|
"io/ioutil" |
|
"path" |
|
"path/filepath" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/Unknwon/com" |
|
"github.com/Unknwon/paginater" |
|
|
|
"github.com/gogits/gogs/models" |
|
"github.com/gogits/gogs/modules/base" |
|
"github.com/gogits/gogs/modules/git" |
|
"github.com/gogits/gogs/modules/log" |
|
"github.com/gogits/gogs/modules/middleware" |
|
"github.com/gogits/gogs/modules/template" |
|
|
|
"github.com/generaltso/linguist" |
|
) |
|
|
|
const ( |
|
HOME base.TplName = "repo/home" |
|
WATCHERS base.TplName = "repo/watchers" |
|
FORKS base.TplName = "repo/forks" |
|
) |
|
|
|
func Home(ctx *middleware.Context) { |
|
ctx.Data["Title"] = ctx.Repo.Repository.Name |
|
ctx.Data["PageIsViewCode"] = true |
|
ctx.Data["RequireHighlightJS"] = true |
|
|
|
branchName := ctx.Repo.BranchName |
|
userName := ctx.Repo.Owner.Name |
|
repoName := ctx.Repo.Repository.Name |
|
|
|
repoLink := ctx.Repo.RepoLink |
|
branchLink := ctx.Repo.RepoLink + "/src/" + branchName |
|
treeLink := branchLink |
|
rawLink := ctx.Repo.RepoLink + "/raw/" + branchName |
|
|
|
// Get tree path |
|
treename := ctx.Repo.TreeName |
|
|
|
if len(treename) > 0 { |
|
if treename[len(treename)-1] == '/' { |
|
ctx.Redirect(repoLink + "/src/" + branchName + "/" + treename[:len(treename)-1]) |
|
return |
|
} |
|
|
|
treeLink += "/" + treename |
|
} |
|
|
|
treePath := treename |
|
if len(treePath) != 0 { |
|
treePath = treePath + "/" |
|
} |
|
|
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treename) |
|
if err != nil && err != git.ErrNotExist { |
|
ctx.Handle(404, "GetTreeEntryByPath", err) |
|
return |
|
} |
|
|
|
if len(treename) != 0 && entry == nil { |
|
ctx.Handle(404, "repo.Home", nil) |
|
return |
|
} |
|
|
|
if entry != nil && !entry.IsDir() { |
|
blob := entry.Blob() |
|
|
|
if dataRc, err := blob.Data(); err != nil { |
|
ctx.Handle(404, "blob.Data", err) |
|
return |
|
} else { |
|
ctx.Data["FileSize"] = blob.Size() |
|
ctx.Data["IsFile"] = true |
|
ctx.Data["FileName"] = blob.Name() |
|
ext := path.Ext(blob.Name()) |
|
if len(ext) > 0 { |
|
ext = ext[1:] |
|
} |
|
ctx.Data["FileExt"] = ext |
|
ctx.Data["FileLink"] = rawLink + "/" + treename |
|
|
|
buf := make([]byte, 1024) |
|
n, _ := dataRc.Read(buf) |
|
if n > 0 { |
|
buf = buf[:n] |
|
} |
|
|
|
_, isTextFile := base.IsTextFile(buf) |
|
_, isImageFile := base.IsImageFile(buf) |
|
ctx.Data["IsFileText"] = isTextFile |
|
|
|
switch { |
|
case isImageFile: |
|
ctx.Data["IsImageFile"] = true |
|
case isTextFile: |
|
d, _ := ioutil.ReadAll(dataRc) |
|
buf = append(buf, d...) |
|
readmeExist := base.IsMarkdownFile(blob.Name()) || base.IsReadmeFile(blob.Name()) |
|
ctx.Data["ReadmeExist"] = readmeExist |
|
if readmeExist { |
|
ctx.Data["FileContent"] = string(base.RenderMarkdown(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) |
|
} else { |
|
if err, content := template.ToUtf8WithErr(buf); err != nil { |
|
if err != nil { |
|
log.Error(4, "Convert content encoding: %s", err) |
|
} |
|
ctx.Data["FileContent"] = string(buf) |
|
} else { |
|
ctx.Data["FileContent"] = content |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
// Directory and file list. |
|
tree, err := ctx.Repo.Commit.SubTree(treename) |
|
if err != nil { |
|
ctx.Handle(404, "SubTree", err) |
|
return |
|
} |
|
|
|
entries, err := tree.ListEntries(treename) |
|
if err != nil { |
|
ctx.Handle(500, "ListEntries", err) |
|
return |
|
} |
|
entries.Sort() |
|
|
|
files := make([][]interface{}, 0, len(entries)) |
|
for _, te := range entries { |
|
if te.Type != git.COMMIT { |
|
c, err := ctx.Repo.Commit.GetCommitOfRelPath(filepath.Join(treePath, te.Name())) |
|
if err != nil { |
|
ctx.Handle(500, "GetCommitOfRelPath", err) |
|
return |
|
} |
|
files = append(files, []interface{}{te, c}) |
|
} else { |
|
sm, err := ctx.Repo.Commit.GetSubModule(path.Join(treename, te.Name())) |
|
if err != nil { |
|
ctx.Handle(500, "GetSubModule", err) |
|
return |
|
} |
|
smUrl := "" |
|
if sm != nil { |
|
smUrl = sm.Url |
|
} |
|
|
|
c, err := ctx.Repo.Commit.GetCommitOfRelPath(filepath.Join(treePath, te.Name())) |
|
if err != nil { |
|
ctx.Handle(500, "GetCommitOfRelPath", err) |
|
return |
|
} |
|
files = append(files, []interface{}{te, git.NewSubModuleFile(c, smUrl, te.ID.String())}) |
|
} |
|
} |
|
ctx.Data["Files"] = files |
|
|
|
var readmeFile *git.Blob |
|
|
|
for _, f := range entries { |
|
if f.IsDir() || !base.IsReadmeFile(f.Name()) { |
|
continue |
|
} else { |
|
readmeFile = f.Blob() |
|
break |
|
} |
|
} |
|
|
|
if readmeFile != nil { |
|
ctx.Data["ReadmeInList"] = true |
|
ctx.Data["ReadmeExist"] = true |
|
if dataRc, err := readmeFile.Data(); err != nil { |
|
ctx.Handle(404, "repo.SinglereadmeFile.Data", err) |
|
return |
|
} else { |
|
|
|
buf := make([]byte, 1024) |
|
n, _ := dataRc.Read(buf) |
|
if n > 0 { |
|
buf = buf[:n] |
|
} |
|
|
|
ctx.Data["FileSize"] = readmeFile.Size() |
|
ctx.Data["FileLink"] = rawLink + "/" + treename |
|
_, isTextFile := base.IsTextFile(buf) |
|
ctx.Data["FileIsText"] = isTextFile |
|
ctx.Data["FileName"] = readmeFile.Name() |
|
if isTextFile { |
|
d, _ := ioutil.ReadAll(dataRc) |
|
buf = append(buf, d...) |
|
switch { |
|
case base.IsMarkdownFile(readmeFile.Name()): |
|
buf = base.RenderMarkdown(buf, treeLink, ctx.Repo.Repository.ComposeMetas()) |
|
default: |
|
buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1) |
|
} |
|
ctx.Data["FileContent"] = string(buf) |
|
} |
|
} |
|
} |
|
|
|
lastCommit := ctx.Repo.Commit |
|
if len(treePath) > 0 { |
|
c, err := ctx.Repo.Commit.GetCommitOfRelPath(treePath) |
|
if err != nil { |
|
ctx.Handle(500, "GetCommitOfRelPath", err) |
|
return |
|
} |
|
lastCommit = c |
|
} |
|
ctx.Data["LastCommit"] = lastCommit |
|
ctx.Data["LastCommitUser"] = models.ValidateCommitWithEmail(lastCommit) |
|
|
|
branchId, err := ctx.Repo.GitRepo.GetCommitIdOfBranch(branchName) |
|
if err != nil || branchId != lastCommit.ID.String() { |
|
branchId = lastCommit.ID.String() |
|
} |
|
Langs := getLanguageStats(ctx, branchId) |
|
ctx.Data["LanguageStats"] = Langs |
|
|
|
} |
|
|
|
ctx.Data["Username"] = userName |
|
ctx.Data["Reponame"] = repoName |
|
|
|
var treenames []string |
|
Paths := make([]string, 0) |
|
|
|
if len(treename) > 0 { |
|
treenames = strings.Split(treename, "/") |
|
for i, _ := range treenames { |
|
Paths = append(Paths, strings.Join(treenames[0:i+1], "/")) |
|
} |
|
|
|
ctx.Data["HasParentPath"] = true |
|
if len(Paths)-2 >= 0 { |
|
ctx.Data["ParentPath"] = "/" + Paths[len(Paths)-2] |
|
} |
|
} |
|
|
|
ctx.Data["Paths"] = Paths |
|
ctx.Data["TreeName"] = treename |
|
ctx.Data["Treenames"] = treenames |
|
ctx.Data["TreePath"] = treePath |
|
ctx.Data["BranchLink"] = branchLink |
|
ctx.HTML(200, HOME) |
|
} |
|
|
|
func renderItems(ctx *middleware.Context, total int, getter func(page int) ([]*models.User, error)) { |
|
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["Watchers"] = items |
|
|
|
ctx.HTML(200, WATCHERS) |
|
} |
|
|
|
func Watchers(ctx *middleware.Context) { |
|
ctx.Data["Title"] = ctx.Tr("repo.watchers") |
|
ctx.Data["PageIsWatchers"] = true |
|
renderItems(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers) |
|
} |
|
|
|
func Stars(ctx *middleware.Context) { |
|
ctx.Data["Title"] = ctx.Tr("repo.stargazers") |
|
ctx.Data["PageIsStargazers"] = true |
|
renderItems(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers) |
|
} |
|
|
|
func Forks(ctx *middleware.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) |
|
} |
|
|
|
func getLanguageStats(ctx *middleware.Context, branchId string) interface{} { |
|
|
|
all_files := linguistlstree(ctx, branchId) |
|
languages := map[string]float64{} |
|
|
|
var total_size float64 |
|
for _, f := range all_files { |
|
languages[f.Language] += f.Size |
|
total_size += f.Size |
|
} |
|
|
|
percent := []float64{} |
|
results := map[float64]string{} |
|
|
|
for lang, size := range languages { |
|
p := size / total_size * 100.0 |
|
percent = append(percent, p) |
|
results[p] = lang |
|
} |
|
|
|
sort.Sort(sort.Reverse(sort.Float64Slice(percent))) |
|
|
|
ret := []*LanguageStat{} |
|
for i, p := range percent { |
|
// limit result set |
|
if i > 10 { |
|
break |
|
} |
|
lang := results[p] |
|
color := linguist.GetColor(lang) |
|
if color == "" { |
|
color = "#ccc" //grey |
|
} |
|
ret = append(ret, &LanguageStat{Name: lang, |
|
Percent: fmt.Sprintf("%.2f%%", p), |
|
Color: color}) |
|
} |
|
return ret |
|
} |
|
|
|
type LanguageStat struct { |
|
Name string |
|
Percent string |
|
Color string |
|
} |
|
|
|
// see below |
|
type file struct { |
|
Name string |
|
Size float64 |
|
Language string |
|
} |
|
|
|
// just some utilities... |
|
func gitcmd(ctx *middleware.Context, args ...string) string { |
|
stdout, _, err := com.ExecCmdDir(ctx.Repo.GitRepo.Path, "git", args...) |
|
tsoErr(ctx, err) |
|
return stdout |
|
} |
|
func gitcmdbytes(ctx *middleware.Context, args ...string) []byte { |
|
stdout, _, err := com.ExecCmdDirBytes(ctx.Repo.GitRepo.Path, "git", args...) |
|
tsoErr(ctx, err) |
|
return stdout |
|
} |
|
func tsoErr(ctx *middleware.Context, err error) { |
|
if err != nil { |
|
ctx.Handle(500, "*blames tso*", err) |
|
} |
|
} |
|
|
|
// returns every file in a tree |
|
// additionally detecting programming language |
|
func linguistlstree(ctx *middleware.Context, treeish string) (files []*file) { |
|
files = []*file{} |
|
lstext := gitcmd(ctx, "ls-tree", treeish) |
|
for _, ln := range strings.Split(lstext, "\n") { |
|
fields := strings.Split(ln, " ") |
|
if len(fields) != 3 { |
|
continue |
|
} |
|
//fmode := fields[0] |
|
ftype := fields[1] |
|
fields = strings.Split(fields[2], "\t") |
|
if len(fields) != 2 { |
|
continue |
|
} |
|
fhash := fields[0] |
|
fname := fields[1] |
|
|
|
switch ftype { |
|
// broken, don't know why |
|
// case "tree": |
|
// subdir := linguistlstree(ctx, fhash) |
|
// files = append(files, subdir...) |
|
case "blob": |
|
// if it's vendored, don't even look at it |
|
// (vendored means files like README.md, .gitignore, etc...) |
|
if linguist.IsVendored(fname) { |
|
continue |
|
} |
|
|
|
ssize := gitcmd(ctx, "cat-file", "-s", fhash) |
|
fsize, err := strconv.ParseFloat(strings.TrimSpace(ssize), 64) |
|
tsoErr(ctx, err) |
|
|
|
// if it's an empty file don't even waste time |
|
if fsize == 0 { |
|
continue |
|
} |
|
|
|
f := &file{} |
|
f.Name = fname |
|
f.Size = fsize |
|
|
|
// |
|
// language detection |
|
// |
|
|
|
// by file extension |
|
by_ext := linguist.DetectFromFilename(fname) |
|
if by_ext != "" { |
|
f.Language = by_ext |
|
files = append(files, f) |
|
continue |
|
} |
|
// by mimetype |
|
// if we can't guess type by extension, then before jumping into |
|
// lexing and parsing things like image files or cat videos |
|
// ...or other binary formats which will give erroneous results... |
|
// ...or other binary formats which will give erroneous results... |
|
// with the linguist.DetectFromContents method, I posit looking |
|
// at mimetype with linguist.DetectMimeFromFilename |
|
// |
|
// ...however, this is not what github does at all, instead ignoring |
|
// binary files altogether. However, there is no law that states |
|
// git must be used for code only. |
|
by_mime, shouldIgnore, _ := linguist.DetectMimeFromFilename(fname) |
|
if by_mime != "" && shouldIgnore { |
|
f.Language = by_mime |
|
files = append(files, f) |
|
continue |
|
} |
|
|
|
// by contents |
|
// see also: github.com/github/linguist |
|
// see also: github.com/generaltso/linguist |
|
contents := gitcmdbytes(ctx, "cat-file", "blob", fhash) |
|
by_contents := linguist.DetectFromContents(contents) |
|
if by_contents != "" { |
|
f.Language = by_contents |
|
} else { |
|
f.Language = "(undetermined)" |
|
} |
|
files = append(files, f) |
|
} |
|
} |
|
return files |
|
}
|
|
|