2017-09-14 09:51:32 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-07-26 08:24:27 +04:00
// 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"
2017-01-29 23:13:57 +03:00
"encoding/base64"
2016-08-09 22:56:00 +03:00
"fmt"
gotemplate "html/template"
2014-07-26 08:24:27 +04:00
"io/ioutil"
"path"
2017-01-29 23:13:57 +03:00
"strconv"
2014-07-26 08:24:27 +04:00
"strings"
2016-11-11 15:11:45 +03:00
"code.gitea.io/git"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2016-12-06 20:58:31 +03:00
"code.gitea.io/gitea/modules/highlight"
2016-12-26 04:16:37 +03:00
"code.gitea.io/gitea/modules/lfs"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2017-04-21 10:01:08 +03:00
"code.gitea.io/gitea/modules/markup"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2016-12-06 20:58:31 +03:00
"code.gitea.io/gitea/modules/templates"
2016-11-11 15:11:45 +03:00
"github.com/Unknwon/paginater"
2014-07-26 08:24:27 +04:00
)
const (
2017-03-18 13:59:07 +03:00
tplRepoBARE base . TplName = "repo/bare"
2016-11-21 13:03:29 +03:00
tplRepoHome base . TplName = "repo/home"
tplWatchers base . TplName = "repo/watchers"
tplForks base . TplName = "repo/forks"
2014-07-26 08:24:27 +04:00
)
2016-08-30 12:08:38 +03:00
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
2016-06-27 11:38:35 +03:00
}
2014-07-26 08:24:27 +04:00
2016-08-30 12:08:38 +03:00
entries , err := tree . ListEntries ( )
if err != nil {
ctx . Handle ( 500 , "ListEntries" , err )
return
2014-07-26 08:24:27 +04:00
}
2017-09-19 11:37:03 +03:00
entries . CustomSort ( base . NaturalSortLess )
2014-07-26 08:24:27 +04:00
2016-08-30 12:08:38 +03:00
ctx . Data [ "Files" ] , err = entries . GetCommitsInfo ( ctx . Repo . Commit , ctx . Repo . TreePath )
2016-08-15 09:02:14 +03:00
if err != nil {
2016-08-30 12:08:38 +03:00
ctx . Handle ( 500 , "GetCommitsInfo" , err )
2014-07-26 08:24:27 +04:00
return
}
2016-08-30 12:08:38 +03:00
var readmeFile * git . Blob
for _ , entry := range entries {
2017-05-02 11:57:54 +03:00
if entry . IsDir ( ) {
continue
}
2017-09-16 20:17:57 +03:00
if ! markup . IsReadmeFile ( entry . Name ( ) ) {
2016-08-30 12:08:38 +03:00
continue
}
readmeFile = entry . Blob ( )
2017-09-16 20:17:57 +03:00
if markup . Type ( entry . Name ( ) ) != "" {
2017-05-02 11:57:54 +03:00
break
}
2016-08-30 12:08:38 +03:00
}
if readmeFile != nil {
2016-08-31 23:59:23 +03:00
ctx . Data [ "RawFileLink" ] = ""
2016-08-30 12:08:38 +03:00
ctx . Data [ "ReadmeInList" ] = true
ctx . Data [ "ReadmeExist" ] = true
2017-11-29 04:50:39 +03:00
dataRc , err := readmeFile . DataAsync ( )
2016-08-15 09:02:14 +03:00
if err != nil {
2016-08-30 12:08:38 +03:00
ctx . Handle ( 500 , "Data" , err )
2014-07-26 08:24:27 +04:00
return
2016-08-15 09:02:14 +03:00
}
2017-11-29 04:50:39 +03:00
defer dataRc . Close ( )
2014-07-26 08:24:27 +04:00
2016-08-15 09:02:14 +03:00
buf := make ( [ ] byte , 1024 )
n , _ := dataRc . Read ( buf )
2016-08-31 23:59:23 +03:00
buf = buf [ : n ]
2016-08-09 22:35:20 +03:00
2016-08-30 12:08:38 +03:00
isTextFile := base . IsTextFile ( buf )
ctx . Data [ "FileIsText" ] = isTextFile
ctx . Data [ "FileName" ] = readmeFile . Name ( )
// FIXME: what happens when README file is an image?
if isTextFile {
2017-11-29 04:50:39 +03:00
if readmeFile . Size ( ) >= setting . UI . MaxDisplayFileSize {
// Pretend that this is a normal text file to display 'This file is too large to be shown'
ctx . Data [ "IsFileTooLarge" ] = true
ctx . Data [ "IsTextFile" ] = true
ctx . Data [ "FileSize" ] = readmeFile . Size ( )
2017-04-21 10:01:08 +03:00
} else {
2017-11-29 04:50:39 +03:00
d , _ := ioutil . ReadAll ( dataRc )
buf = append ( buf , d ... )
if markup . Type ( readmeFile . Name ( ) ) != "" {
ctx . Data [ "IsMarkup" ] = true
ctx . Data [ "FileContent" ] = string ( markup . Render ( readmeFile . Name ( ) , buf , treeLink , ctx . Repo . Repository . ComposeMetas ( ) ) )
} else {
ctx . Data [ "IsRenderedHTML" ] = true
ctx . Data [ "FileContent" ] = string ( bytes . Replace ( buf , [ ] byte ( "\n" ) , [ ] byte ( ` <br> ` ) , - 1 ) )
}
2014-07-26 08:24:27 +04:00
}
2016-08-15 09:02:14 +03:00
}
2016-08-30 12:08:38 +03:00
}
2016-08-15 09:02:14 +03:00
2016-08-30 12:08:38 +03:00
// 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 )
2014-07-26 08:24:27 +04:00
if err != nil {
2016-08-30 12:08:38 +03:00
ctx . Handle ( 500 , "GetCommitByPath" , err )
2014-07-26 08:24:27 +04:00
return
}
2016-08-30 12:08:38 +03:00
}
ctx . Data [ "LatestCommit" ] = latestCommit
2017-05-05 10:15:36 +03:00
ctx . Data [ "LatestCommitVerification" ] = models . ParseCommitWithSignature ( latestCommit )
2016-08-30 12:08:38 +03:00
ctx . Data [ "LatestCommitUser" ] = models . ValidateCommitWithEmail ( latestCommit )
2017-09-14 09:51:32 +03:00
statuses , err := models . GetLatestCommitStatus ( ctx . Repo . Repository , ctx . Repo . Commit . ID . String ( ) , 0 )
if err != nil {
log . Error ( 3 , "GetLatestCommitStatus: %v" , err )
}
ctx . Data [ "LatestCommitStatus" ] = models . CalcCommitStatus ( statuses )
2016-08-30 12:08:38 +03:00
// Check permission to add or upload new file.
if ctx . Repo . IsWriter ( ) && ctx . Repo . IsViewBranch {
ctx . Data [ "CanAddFile" ] = true
2016-08-30 15:07:50 +03:00
ctx . Data [ "CanUploadFile" ] = setting . Repository . Upload . Enabled
2016-08-30 12:08:38 +03:00
}
}
2014-09-30 12:39:53 +04:00
2016-08-30 12:08:38 +03:00
func renderFile ( ctx * context . Context , entry * git . TreeEntry , treeLink , rawLink string ) {
ctx . Data [ "IsViewFile" ] = true
2014-07-26 08:24:27 +04:00
2016-08-30 12:08:38 +03:00
blob := entry . Blob ( )
2017-11-29 04:50:39 +03:00
dataRc , err := blob . DataAsync ( )
2016-08-30 12:08:38 +03:00
if err != nil {
2017-11-29 04:50:39 +03:00
ctx . Handle ( 500 , "DataAsync" , err )
2016-08-30 12:08:38 +03:00
return
}
2017-11-29 04:50:39 +03:00
defer dataRc . Close ( )
2014-07-26 08:24:27 +04:00
2016-08-30 12:08:38 +03:00
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 )
2016-08-31 23:59:23 +03:00
buf = buf [ : n ]
2016-08-30 12:08:38 +03:00
isTextFile := base . IsTextFile ( buf )
ctx . Data [ "IsTextFile" ] = isTextFile
2016-12-26 04:16:37 +03:00
//Check for LFS meta file
if isTextFile && setting . LFS . StartServer {
headString := string ( buf )
if strings . HasPrefix ( headString , models . LFSMetaFileIdentifier ) {
splitLines := strings . Split ( headString , "\n" )
if len ( splitLines ) >= 3 {
oid := strings . TrimPrefix ( splitLines [ 1 ] , models . LFSMetaFileOidPrefix )
size , err := strconv . ParseInt ( strings . TrimPrefix ( splitLines [ 2 ] , "size " ) , 10 , 64 )
if len ( oid ) == 64 && err == nil {
contentStore := & lfs . ContentStore { BasePath : setting . LFS . ContentPath }
meta := & models . LFSMetaObject { Oid : oid }
if contentStore . Exists ( meta ) {
ctx . Data [ "IsTextFile" ] = false
isTextFile = false
ctx . Data [ "IsLFSFile" ] = true
ctx . Data [ "FileSize" ] = size
filenameBase64 := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( blob . Name ( ) ) )
ctx . Data [ "RawFileLink" ] = fmt . Sprintf ( "%s%s/info/lfs/objects/%s/%s" , setting . AppURL , ctx . Repo . Repository . FullName ( ) , oid , filenameBase64 )
}
}
}
}
}
2016-08-30 12:08:38 +03:00
// Assume file is not editable first.
if ! isTextFile {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_non_text_files" )
}
switch {
case isTextFile :
if blob . Size ( ) >= setting . UI . MaxDisplayFileSize {
ctx . Data [ "IsFileTooLarge" ] = true
break
2014-07-26 08:24:27 +04:00
}
2016-08-30 12:08:38 +03:00
d , _ := ioutil . ReadAll ( dataRc )
buf = append ( buf , d ... )
2014-07-26 08:24:27 +04:00
2017-09-21 08:20:14 +03:00
readmeExist := markup . IsReadmeFile ( blob . Name ( ) )
2016-08-30 12:08:38 +03:00
ctx . Data [ "ReadmeExist" ] = readmeExist
2017-10-16 10:04:34 +03:00
if markup . Type ( blob . Name ( ) ) != "" {
2017-10-17 02:17:22 +03:00
ctx . Data [ "IsMarkup" ] = true
2017-04-21 10:01:08 +03:00
ctx . Data [ "FileContent" ] = string ( markup . Render ( blob . Name ( ) , buf , path . Dir ( treeLink ) , ctx . Repo . Repository . ComposeMetas ( ) ) )
2017-09-21 21:24:19 +03:00
} else if readmeExist {
2017-10-16 10:04:34 +03:00
ctx . Data [ "IsRenderedHTML" ] = true
2017-09-21 21:24:19 +03:00
ctx . Data [ "FileContent" ] = string ( bytes . Replace ( buf , [ ] byte ( "\n" ) , [ ] byte ( ` <br> ` ) , - 1 ) )
2016-08-30 12:08:38 +03:00
} else {
// Building code view blocks with line number on server side.
var fileContent string
2016-12-06 20:58:31 +03:00
if content , err := templates . ToUTF8WithErr ( buf ) ; err != nil {
2016-08-30 12:08:38 +03:00
if err != nil {
2017-01-29 23:13:57 +03:00
log . Error ( 4 , "ToUTF8WithErr: %v" , err )
2014-07-26 08:24:27 +04:00
}
2016-08-30 12:08:38 +03:00
fileContent = string ( buf )
} else {
fileContent = content
2014-07-26 08:24:27 +04:00
}
2014-09-26 16:55:13 +04:00
2016-08-30 12:08:38 +03:00
var output bytes . Buffer
lines := strings . Split ( fileContent , "\n" )
for index , line := range lines {
2017-06-10 18:20:25 +03:00
line = gotemplate . HTMLEscapeString ( line )
if index != len ( lines ) - 1 {
line += "\n"
}
output . WriteString ( fmt . Sprintf ( ` <li class="L%d" rel="L%d">%s</li> ` , index + 1 , index + 1 , line ) )
2014-09-26 16:55:13 +04:00
}
2016-08-30 12:08:38 +03:00
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 ( ) )
2014-09-26 16:55:13 +04:00
}
2016-08-30 12:08:38 +03:00
if ctx . Repo . 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" )
2016-08-28 11:41:44 +03:00
}
2016-08-30 12:08:38 +03:00
case base . IsPDFFile ( buf ) :
ctx . Data [ "IsPDFFile" ] = true
2016-12-20 11:09:11 +03:00
case base . IsVideoFile ( buf ) :
ctx . Data [ "IsVideoFile" ] = true
2016-08-30 12:08:38 +03:00
case base . IsImageFile ( buf ) :
ctx . Data [ "IsImageFile" ] = true
2014-07-26 08:24:27 +04:00
}
2016-08-30 12:08:38 +03:00
if ctx . Repo . 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" )
}
}
2016-11-21 13:03:29 +03:00
// Home render repository home page
2016-08-30 12:08:38 +03:00
func Home ( ctx * context . Context ) {
2017-05-18 17:54:24 +03:00
if len ( ctx . Repo . Repository . Units ) > 0 {
2017-10-01 16:50:56 +03:00
var firstUnit * models . Unit
for _ , repoUnit := range ctx . Repo . Repository . Units {
if repoUnit . Type == models . UnitTypeCode {
renderCode ( ctx )
return
}
unit , ok := models . Units [ repoUnit . Type ]
if ok && ( firstUnit == nil || ! firstUnit . IsLessThan ( unit ) ) {
firstUnit = & unit
}
2017-05-18 17:54:24 +03:00
}
2017-10-01 16:50:56 +03:00
if firstUnit != nil {
ctx . Redirect ( fmt . Sprintf ( "%s/%s%s" , setting . AppSubURL , ctx . Repo . Repository . FullName ( ) , firstUnit . URI ) )
2017-05-18 17:54:24 +03:00
return
}
}
ctx . Handle ( 404 , "Home" , fmt . Errorf ( ctx . Tr ( "units.error.no_unit_allowed_repo" ) ) )
}
func renderCode ( ctx * context . Context ) {
2017-03-18 13:59:07 +03:00
ctx . Data [ "PageIsViewCode" ] = true
if ctx . Repo . Repository . IsBare {
ctx . HTML ( 200 , tplRepoBARE )
return
}
2016-08-30 12:08:38 +03:00
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
ctx . Data [ "RequireHighlightJS" ] = true
2017-10-30 05:04:25 +03:00
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
2016-08-30 12:08:38 +03:00
treeLink := branchLink
2017-10-30 05:04:25 +03:00
rawLink := ctx . Repo . RepoLink + "/raw/" + ctx . Repo . BranchNameSubURL ( )
2016-08-30 12:08:38 +03:00
if len ( ctx . Repo . TreePath ) > 0 {
treeLink += "/" + ctx . Repo . TreePath
}
// 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
}
2014-07-26 08:24:27 +04:00
2016-08-30 12:08:38 +03:00
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 ] , "/" ) )
2014-07-26 08:24:27 +04:00
}
ctx . Data [ "HasParentPath" ] = true
2016-08-09 22:56:00 +03:00
if len ( paths ) - 2 >= 0 {
ctx . Data [ "ParentPath" ] = "/" + paths [ len ( paths ) - 2 ]
2014-07-26 08:24:27 +04:00
}
}
2016-08-09 22:56:00 +03:00
ctx . Data [ "Paths" ] = paths
2016-08-28 01:25:01 +03:00
ctx . Data [ "TreeLink" ] = treeLink
2016-08-30 12:08:38 +03:00
ctx . Data [ "TreeNames" ] = treeNames
2014-07-26 08:24:27 +04:00
ctx . Data [ "BranchLink" ] = branchLink
2016-11-21 13:03:29 +03:00
ctx . HTML ( 200 , tplRepoHome )
2014-07-26 08:24:27 +04:00
}
2015-11-17 07:28:46 +03:00
2017-02-28 07:56:15 +03:00
// RenderUserCards render a page show users according the input templaet
2016-03-11 19:56:52 +03:00
func RenderUserCards ( ctx * context . Context , total int , getter func ( page int ) ( [ ] * models . User , error ) , tpl base . TplName ) {
2015-11-17 07:28:46 +03:00
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
}
2015-12-21 15:24:11 +03:00
ctx . Data [ "Cards" ] = items
2015-11-17 07:28:46 +03:00
2015-12-21 15:24:11 +03:00
ctx . HTML ( 200 , tpl )
2015-11-17 07:28:46 +03:00
}
2016-11-21 13:03:29 +03:00
// Watchers render repository's watch users
2016-03-11 19:56:52 +03:00
func Watchers ( ctx * context . Context ) {
2015-11-17 07:28:46 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.watchers" )
2015-12-21 15:24:11 +03:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.watchers" )
2015-11-17 07:28:46 +03:00
ctx . Data [ "PageIsWatchers" ] = true
2016-11-21 13:03:29 +03:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumWatches , ctx . Repo . Repository . GetWatchers , tplWatchers )
2015-11-17 07:28:46 +03:00
}
2016-11-21 13:03:29 +03:00
// Stars render repository's starred users
2016-03-11 19:56:52 +03:00
func Stars ( ctx * context . Context ) {
2015-11-17 07:28:46 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.stargazers" )
2015-12-21 15:24:11 +03:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.stargazers" )
2015-11-17 07:28:46 +03:00
ctx . Data [ "PageIsStargazers" ] = true
2016-11-21 13:03:29 +03:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumStars , ctx . Repo . Repository . GetStargazers , tplWatchers )
2015-11-17 07:28:46 +03:00
}
2015-11-17 07:33:40 +03:00
2016-11-21 13:03:29 +03:00
// Forks render repository's forked users
2016-03-11 19:56:52 +03:00
func Forks ( ctx * context . Context ) {
2015-11-17 07:33:40 +03:00
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
2016-11-21 13:03:29 +03:00
ctx . HTML ( 200 , tplForks )
2015-11-17 07:33:40 +03:00
}