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"
2021-01-22 20:49:13 +03:00
"io"
2014-07-26 08:24:27 +04:00
"io/ioutil"
2021-04-05 18:30:52 +03:00
"net/http"
2019-10-13 16:23:14 +03:00
"net/url"
2014-07-26 08:24:27 +04:00
"path"
2020-07-01 00:34:03 +03:00
"strconv"
2014-07-26 08:24:27 +04:00
"strings"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
2020-02-01 22:11:32 +03:00
"code.gitea.io/gitea/modules/cache"
2019-08-15 15:07:28 +03:00
"code.gitea.io/gitea/modules/charset"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/context"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
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"
2021-06-30 22:14:53 +03:00
"code.gitea.io/gitea/modules/structs"
2021-06-05 15:32:19 +03:00
"code.gitea.io/gitea/modules/typesniffer"
2014-07-26 08:24:27 +04:00
)
const (
2019-01-18 03:01:04 +03:00
tplRepoEMPTY base . TplName = "repo/empty"
tplRepoHome base . TplName = "repo/home"
tplWatchers base . TplName = "repo/watchers"
tplForks base . TplName = "repo/forks"
2020-09-09 21:29:10 +03:00
tplMigrating base . TplName = "repo/migrate/migrating"
2014-07-26 08:24:27 +04:00
)
2020-02-22 02:04:20 +03:00
type namedBlob struct {
name string
isSymlink bool
blob * git . Blob
}
2020-07-01 00:34:03 +03:00
func linesBytesCount ( s [ ] byte ) int {
nl := [ ] byte { '\n' }
n := bytes . Count ( s , nl )
if len ( s ) > 0 && ! bytes . HasSuffix ( s , nl ) {
n ++
}
return n
}
2020-02-22 02:04:20 +03:00
// FIXME: There has to be a more efficient way of doing this
func getReadmeFileFromPath ( commit * git . Commit , treePath string ) ( * namedBlob , error ) {
tree , err := commit . SubTree ( treePath )
if err != nil {
return nil , err
}
entries , err := tree . ListEntries ( )
if err != nil {
return nil , err
}
var readmeFiles [ 4 ] * namedBlob
var exts = [ ] string { ".md" , ".txt" , "" } // sorted by priority
for _ , entry := range entries {
if entry . IsDir ( ) {
continue
}
for i , ext := range exts {
if markup . IsReadmeFile ( entry . Name ( ) , ext ) {
if readmeFiles [ i ] == nil || base . NaturalSortLess ( readmeFiles [ i ] . name , entry . Blob ( ) . Name ( ) ) {
name := entry . Name ( )
isSymlink := entry . IsLink ( )
target := entry
if isSymlink {
target , err = entry . FollowLinks ( )
if err != nil && ! git . IsErrBadLink ( err ) {
return nil , err
}
}
if target != nil && ( target . IsExecutable ( ) || target . IsRegular ( ) ) {
readmeFiles [ i ] = & namedBlob {
name ,
isSymlink ,
target . Blob ( ) ,
}
}
}
}
}
if markup . IsReadmeFile ( entry . Name ( ) ) {
if readmeFiles [ 3 ] == nil || base . NaturalSortLess ( readmeFiles [ 3 ] . name , entry . Blob ( ) . Name ( ) ) {
name := entry . Name ( )
isSymlink := entry . IsLink ( )
if isSymlink {
entry , err = entry . FollowLinks ( )
if err != nil && ! git . IsErrBadLink ( err ) {
return nil , err
}
}
if entry != nil && ( entry . IsExecutable ( ) || entry . IsRegular ( ) ) {
readmeFiles [ 3 ] = & namedBlob {
name ,
isSymlink ,
entry . Blob ( ) ,
}
}
}
}
}
var readmeFile * namedBlob
for _ , f := range readmeFiles {
if f != nil {
readmeFile = f
break
}
}
return readmeFile , nil
}
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 {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ListEntries" , err )
2016-08-30 12:08:38 +03:00
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
2020-12-17 17:00:47 +03:00
var c * git . LastCommitCache
2020-02-01 22:11:32 +03:00
if setting . CacheService . LastCommit . Enabled && ctx . Repo . CommitsCount >= setting . CacheService . LastCommit . CommitsCount {
2021-02-10 01:29:03 +03:00
c = git . NewLastCommitCache ( ctx . Repo . Repository . FullName ( ) , ctx . Repo . GitRepo , setting . LastCommitCacheTTLSeconds , cache . GetCache ( ) )
2020-02-01 22:11:32 +03:00
}
2019-04-19 15:17:27 +03:00
var latestCommit * git . Commit
2021-06-07 02:44:58 +03:00
ctx . Data [ "Files" ] , latestCommit , err = entries . GetCommitsInfo ( ctx , ctx . Repo . Commit , ctx . Repo . TreePath , c )
2016-08-15 09:02:14 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetCommitsInfo" , err )
2014-07-26 08:24:27 +04:00
return
}
2019-01-14 22:15:06 +03:00
// 3 for the extensions in exts[] in order
// the last one is for a readme that doesn't
// strictly match an extension
2020-02-22 02:04:20 +03:00
var readmeFiles [ 4 ] * namedBlob
var docsEntries [ 3 ] * git . TreeEntry
2019-01-14 22:15:06 +03:00
var exts = [ ] string { ".md" , ".txt" , "" } // sorted by priority
2016-08-30 12:08:38 +03:00
for _ , entry := range entries {
2017-05-02 11:57:54 +03:00
if entry . IsDir ( ) {
2020-02-22 02:04:20 +03:00
lowerName := strings . ToLower ( entry . Name ( ) )
switch lowerName {
case "docs" :
if entry . Name ( ) == "docs" || docsEntries [ 0 ] == nil {
docsEntries [ 0 ] = entry
}
case ".gitea" :
if entry . Name ( ) == ".gitea" || docsEntries [ 1 ] == nil {
docsEntries [ 1 ] = entry
}
case ".github" :
if entry . Name ( ) == ".github" || docsEntries [ 2 ] == nil {
docsEntries [ 2 ] = entry
}
}
2017-05-02 11:57:54 +03:00
continue
}
2019-01-14 22:15:06 +03:00
for i , ext := range exts {
if markup . IsReadmeFile ( entry . Name ( ) , ext ) {
2020-02-22 02:04:20 +03:00
log . Debug ( "%s" , entry . Name ( ) )
name := entry . Name ( )
isSymlink := entry . IsLink ( )
target := entry
if isSymlink {
target , err = entry . FollowLinks ( )
if err != nil && ! git . IsErrBadLink ( err ) {
ctx . ServerError ( "FollowLinks" , err )
return
}
}
log . Debug ( "%t" , target == nil )
if target != nil && ( target . IsExecutable ( ) || target . IsRegular ( ) ) {
readmeFiles [ i ] = & namedBlob {
name ,
isSymlink ,
target . Blob ( ) ,
}
}
2019-01-14 22:15:06 +03:00
}
}
if markup . IsReadmeFile ( entry . Name ( ) ) {
2020-02-22 02:04:20 +03:00
name := entry . Name ( )
isSymlink := entry . IsLink ( )
if isSymlink {
entry , err = entry . FollowLinks ( )
if err != nil && ! git . IsErrBadLink ( err ) {
ctx . ServerError ( "FollowLinks" , err )
return
}
}
if entry != nil && ( entry . IsExecutable ( ) || entry . IsRegular ( ) ) {
readmeFiles [ 3 ] = & namedBlob {
name ,
isSymlink ,
entry . Blob ( ) ,
}
}
2016-08-30 12:08:38 +03:00
}
2019-01-14 22:15:06 +03:00
}
2016-08-30 12:08:38 +03:00
2020-02-22 02:04:20 +03:00
var readmeFile * namedBlob
readmeTreelink := treeLink
2019-01-14 22:15:06 +03:00
for _ , f := range readmeFiles {
if f != nil {
readmeFile = f
2017-05-02 11:57:54 +03:00
break
}
2016-08-30 12:08:38 +03:00
}
2020-02-22 02:04:20 +03:00
if ctx . Repo . TreePath == "" && readmeFile == nil {
for _ , entry := range docsEntries {
if entry == nil {
continue
}
readmeFile , err = getReadmeFileFromPath ( ctx . Repo . Commit , entry . GetSubJumpablePathName ( ) )
if err != nil {
ctx . ServerError ( "getReadmeFileFromPath" , err )
return
}
if readmeFile != nil {
readmeFile . name = entry . Name ( ) + "/" + readmeFile . name
readmeTreelink = treeLink + "/" + entry . GetSubJumpablePathName ( )
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
2020-02-22 02:04:20 +03:00
ctx . Data [ "FileIsSymlink" ] = readmeFile . isSymlink
2016-08-30 12:08:38 +03:00
2020-02-22 02:04:20 +03:00
dataRc , err := readmeFile . blob . DataAsync ( )
2016-08-15 09:02:14 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "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
2021-06-05 15:32:19 +03:00
st := typesniffer . DetectContentType ( buf )
isTextFile := st . IsText ( )
2016-08-30 12:08:38 +03:00
ctx . Data [ "FileIsText" ] = isTextFile
2020-02-22 02:04:20 +03:00
ctx . Data [ "FileName" ] = readmeFile . name
2019-02-21 23:57:16 +03:00
fileSize := int64 ( 0 )
isLFSFile := false
ctx . Data [ "IsLFSFile" ] = false
2016-08-30 12:08:38 +03:00
// FIXME: what happens when README file is an image?
2019-02-21 23:57:16 +03:00
if isTextFile && setting . LFS . StartServer {
2021-04-09 01:25:57 +03:00
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
if pointer . IsValid ( ) {
meta , err := ctx . Repo . Repository . GetLFSMetaObjectByOid ( pointer . Oid )
2019-02-21 23:57:16 +03:00
if err != nil && err != models . ErrLFSObjectNotExist {
ctx . ServerError ( "GetLFSMetaObject" , err )
return
}
2021-04-09 01:25:57 +03:00
if meta != nil {
ctx . Data [ "IsLFSFile" ] = true
isLFSFile = true
// OK read the lfs object
var err error
dataRc , err = lfs . ReadMetaObject ( pointer )
if err != nil {
ctx . ServerError ( "ReadMetaObject" , err )
return
}
defer dataRc . Close ( )
2019-02-21 23:57:16 +03:00
2021-04-09 01:25:57 +03:00
buf = make ( [ ] byte , 1024 )
n , err = dataRc . Read ( buf )
if err != nil {
ctx . ServerError ( "Data" , err )
return
}
buf = buf [ : n ]
2019-02-21 23:57:16 +03:00
2021-06-05 15:32:19 +03:00
st = typesniffer . DetectContentType ( buf )
isTextFile = st . IsText ( )
2021-04-09 01:25:57 +03:00
ctx . Data [ "IsTextFile" ] = isTextFile
2019-02-21 23:57:16 +03:00
2021-04-09 01:25:57 +03:00
fileSize = meta . Size
ctx . Data [ "FileSize" ] = meta . Size
filenameBase64 := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( readmeFile . name ) )
ctx . Data [ "RawFileLink" ] = fmt . Sprintf ( "%s%s.git/info/lfs/objects/%s/%s" , setting . AppURL , ctx . Repo . Repository . FullName ( ) , meta . Oid , filenameBase64 )
2019-02-21 23:57:16 +03:00
}
}
}
if ! isLFSFile {
2020-02-22 02:04:20 +03:00
fileSize = readmeFile . blob . Size ( )
2019-02-21 23:57:16 +03:00
}
2016-08-30 12:08:38 +03:00
if isTextFile {
2019-02-21 23:57:16 +03:00
if fileSize >= setting . UI . MaxDisplayFileSize {
2017-11-29 04:50:39 +03:00
// 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
2019-02-21 23:57:16 +03:00
ctx . Data [ "FileSize" ] = fileSize
2017-04-21 10:01:08 +03:00
} else {
2021-04-20 01:25:08 +03:00
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) )
2018-09-29 11:33:54 +03:00
2020-02-22 02:04:20 +03:00
if markupType := markup . Type ( readmeFile . name ) ; markupType != "" {
2017-11-29 04:50:39 +03:00
ctx . Data [ "IsMarkup" ] = true
2019-08-16 01:09:50 +03:00
ctx . Data [ "MarkupType" ] = string ( markupType )
2021-04-20 01:25:08 +03:00
var result strings . Builder
err := markup . Render ( & markup . RenderContext {
2021-07-13 00:13:59 +03:00
Ctx : ctx ,
2021-04-20 01:25:08 +03:00
Filename : readmeFile . name ,
URLPrefix : readmeTreelink ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ) ,
2021-06-21 01:39:12 +03:00
GitRepo : ctx . Repo . GitRepo ,
2021-04-20 01:25:08 +03:00
} , rd , & result )
if err != nil {
log . Error ( "Render failed: %v then fallback" , err )
bs , _ := ioutil . ReadAll ( rd )
ctx . Data [ "FileContent" ] = strings . ReplaceAll (
gotemplate . HTMLEscapeString ( string ( bs ) ) , "\n" , ` <br> ` ,
)
} else {
ctx . Data [ "FileContent" ] = result . String ( )
}
2017-11-29 04:50:39 +03:00
} else {
ctx . Data [ "IsRenderedHTML" ] = true
2020-10-11 23:27:20 +03:00
ctx . Data [ "FileContent" ] = strings . ReplaceAll (
gotemplate . HTMLEscapeString ( string ( buf ) ) , "\n" , ` <br> ` ,
2018-06-10 21:42:16 +03:00
)
2017-11-29 04:50:39 +03:00
}
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.
ctx . Data [ "LatestCommit" ] = latestCommit
2020-02-27 22:20:55 +03:00
verification := models . ParseCommitWithSignature ( latestCommit )
if err := models . CalculateTrustStatus ( verification , ctx . Repo . Repository , nil ) ; err != nil {
ctx . ServerError ( "CalculateTrustStatus" , err )
return
}
ctx . Data [ "LatestCommitVerification" ] = verification
2016-08-30 12:08:38 +03:00
ctx . Data [ "LatestCommitUser" ] = models . ValidateCommitWithEmail ( latestCommit )
2020-12-18 06:33:32 +03:00
statuses , err := models . GetLatestCommitStatus ( ctx . Repo . Repository . ID , ctx . Repo . Commit . ID . String ( ) , models . ListOptions { } )
2017-09-14 09:51:32 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetLatestCommitStatus: %v" , err )
2017-09-14 09:51:32 +03:00
}
ctx . Data [ "LatestCommitStatus" ] = models . CalcCommitStatus ( statuses )
2020-12-20 06:13:12 +03:00
ctx . Data [ "LatestCommitStatuses" ] = statuses
2017-09-14 09:51:32 +03:00
2016-08-30 12:08:38 +03:00
// Check permission to add or upload new file.
2018-11-28 14:26:14 +03:00
if ctx . Repo . CanWrite ( models . UnitTypeCode ) && ctx . Repo . IsViewBranch {
2019-01-23 21:58:38 +03:00
ctx . Data [ "CanAddFile" ] = ! ctx . Repo . Repository . IsArchived
ctx . Data [ "CanUploadFile" ] = setting . Repository . Upload . Enabled && ! ctx . Repo . Repository . IsArchived
2016-08-30 12:08:38 +03:00
}
2020-09-08 03:08:10 +03:00
ctx . Data [ "SSHDomain" ] = setting . SSH . Domain
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
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 {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "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
2018-09-10 20:01:49 +03:00
ctx . Data [ "Title" ] = ctx . Data [ "Title" ] . ( string ) + " - " + ctx . Repo . TreePath + " at " + ctx . Repo . BranchName
2019-02-21 23:57:16 +03:00
fileSize := blob . Size ( )
2020-02-22 02:04:20 +03:00
ctx . Data [ "FileIsSymlink" ] = entry . IsLink ( )
2016-08-30 12:08:38 +03:00
ctx . Data [ "FileName" ] = 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
2021-06-05 15:32:19 +03:00
st := typesniffer . DetectContentType ( buf )
isTextFile := st . IsText ( )
2019-02-12 18:09:43 +03:00
isLFSFile := false
2021-07-29 04:42:15 +03:00
isDisplayingSource := ctx . Form ( "display" ) == "source"
2021-01-13 06:45:19 +03:00
isDisplayingRendered := ! isDisplayingSource
2016-12-26 04:16:37 +03:00
//Check for LFS meta file
2019-02-21 23:57:16 +03:00
if isTextFile && setting . LFS . StartServer {
2021-04-09 01:25:57 +03:00
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
if pointer . IsValid ( ) {
meta , err := ctx . Repo . Repository . GetLFSMetaObjectByOid ( pointer . Oid )
2019-02-21 23:57:16 +03:00
if err != nil && err != models . ErrLFSObjectNotExist {
ctx . ServerError ( "GetLFSMetaObject" , err )
return
}
2021-04-09 01:25:57 +03:00
if meta != nil {
isLFSFile = true
2019-02-21 23:57:16 +03:00
2021-04-09 01:25:57 +03:00
// OK read the lfs object
var err error
dataRc , err = lfs . ReadMetaObject ( pointer )
if err != nil {
ctx . ServerError ( "ReadMetaObject" , err )
return
}
defer dataRc . Close ( )
buf = make ( [ ] byte , 1024 )
n , err = dataRc . Read ( buf )
// Error EOF don't mean there is an error, it just means we read to
// the end
if err != nil && err != io . EOF {
ctx . ServerError ( "Data" , err )
return
}
buf = buf [ : n ]
2019-02-12 18:09:43 +03:00
2021-06-05 15:32:19 +03:00
st = typesniffer . DetectContentType ( buf )
isTextFile = st . IsText ( )
2021-04-09 01:25:57 +03:00
fileSize = meta . Size
ctx . Data [ "RawFileLink" ] = fmt . Sprintf ( "%s/media/%s/%s" , ctx . Repo . RepoLink , ctx . Repo . BranchNameSubURL ( ) , ctx . Repo . TreePath )
}
2016-12-26 04:16:37 +03:00
}
}
2021-02-05 04:29:42 +03:00
2021-06-05 15:32:19 +03:00
isRepresentableAsText := st . IsRepresentableAsText ( )
2021-02-05 04:29:42 +03:00
if ! isRepresentableAsText {
// If we can't show plain text, always try to render.
isDisplayingSource = false
isDisplayingRendered = true
}
ctx . Data [ "IsLFSFile" ] = isLFSFile
ctx . Data [ "FileSize" ] = fileSize
ctx . Data [ "IsTextFile" ] = isTextFile
ctx . Data [ "IsRepresentableAsText" ] = isRepresentableAsText
ctx . Data [ "IsDisplayingSource" ] = isDisplayingSource
ctx . Data [ "IsDisplayingRendered" ] = isDisplayingRendered
ctx . Data [ "IsTextSource" ] = isTextFile || isDisplayingSource
2019-10-30 00:32:21 +03:00
// Check LFS Lock
lfsLock , err := ctx . Repo . Repository . GetTreePathLock ( ctx . Repo . TreePath )
ctx . Data [ "LFSLock" ] = lfsLock
if err != nil {
ctx . ServerError ( "GetTreePathLock" , err )
return
}
if lfsLock != nil {
ctx . Data [ "LFSLockOwner" ] = lfsLock . Owner . DisplayName ( )
ctx . Data [ "LFSLockHint" ] = ctx . Tr ( "repo.editor.this_file_locked" )
}
2016-12-26 04:16:37 +03:00
2016-08-30 12:08:38 +03:00
// Assume file is not editable first.
2019-02-12 18:09:43 +03:00
if isLFSFile {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_lfs_files" )
2021-01-13 06:45:19 +03:00
} else if ! isRepresentableAsText {
2016-08-30 12:08:38 +03:00
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_non_text_files" )
}
switch {
2021-01-13 06:45:19 +03:00
case isRepresentableAsText :
2021-06-05 15:32:19 +03:00
if st . IsSvgImage ( ) {
2021-01-13 06:45:19 +03:00
ctx . Data [ "IsImageFile" ] = true
ctx . Data [ "HasSourceRenderedToggle" ] = true
}
2019-02-21 23:57:16 +03:00
if fileSize >= setting . UI . MaxDisplayFileSize {
2016-08-30 12:08:38 +03:00
ctx . Data [ "IsFileTooLarge" ] = true
break
2014-07-26 08:24:27 +04:00
}
2021-04-20 01:25:08 +03:00
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) )
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
2019-08-16 01:09:50 +03:00
if markupType := markup . Type ( blob . Name ( ) ) ; markupType != "" {
2017-10-17 02:17:22 +03:00
ctx . Data [ "IsMarkup" ] = true
2019-08-16 01:09:50 +03:00
ctx . Data [ "MarkupType" ] = markupType
2021-04-20 01:25:08 +03:00
var result strings . Builder
err := markup . Render ( & markup . RenderContext {
2021-07-13 00:13:59 +03:00
Ctx : ctx ,
2021-04-20 01:25:08 +03:00
Filename : blob . Name ( ) ,
URLPrefix : path . Dir ( treeLink ) ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ) ,
2021-06-21 01:39:12 +03:00
GitRepo : ctx . Repo . GitRepo ,
2021-04-20 01:25:08 +03:00
} , rd , & result )
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
ctx . Data [ "FileContent" ] = result . String ( )
2017-09-21 21:24:19 +03:00
} else if readmeExist {
2021-04-28 23:22:54 +03:00
buf , _ := ioutil . ReadAll ( rd )
2017-10-16 10:04:34 +03:00
ctx . Data [ "IsRenderedHTML" ] = true
2020-10-11 23:27:20 +03:00
ctx . Data [ "FileContent" ] = strings . ReplaceAll (
gotemplate . HTMLEscapeString ( string ( buf ) ) , "\n" , ` <br> ` ,
2018-06-10 21:42:16 +03:00
)
2016-08-30 12:08:38 +03:00
} else {
2021-04-20 01:25:08 +03:00
buf , _ := ioutil . ReadAll ( rd )
2020-07-01 00:34:03 +03:00
lineNums := linesBytesCount ( buf )
ctx . Data [ "NumLines" ] = strconv . Itoa ( lineNums )
2019-10-29 19:05:26 +03:00
ctx . Data [ "NumLinesSet" ] = true
2020-07-01 00:34:03 +03:00
ctx . Data [ "FileContent" ] = highlight . File ( lineNums , blob . Name ( ) , buf )
2014-09-26 16:55:13 +04:00
}
2019-02-12 18:09:43 +03:00
if ! isLFSFile {
if ctx . Repo . CanEnableEditor ( ) {
2019-10-30 00:32:21 +03:00
if lfsLock != nil && lfsLock . OwnerID != ctx . User . ID {
ctx . Data [ "CanEditFile" ] = false
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.this_file_locked" )
} else {
ctx . Data [ "CanEditFile" ] = true
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.edit_this_file" )
}
2019-02-12 18:09:43 +03:00
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
} else if ! ctx . Repo . CanWrite ( models . UnitTypeCode ) {
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
2021-06-05 15:32:19 +03:00
case st . IsPDF ( ) :
2016-08-30 12:08:38 +03:00
ctx . Data [ "IsPDFFile" ] = true
2021-06-05 15:32:19 +03:00
case st . IsVideo ( ) :
2016-12-20 11:09:11 +03:00
ctx . Data [ "IsVideoFile" ] = true
2021-06-05 15:32:19 +03:00
case st . IsAudio ( ) :
2018-10-30 05:17:26 +03:00
ctx . Data [ "IsAudioFile" ] = true
2021-06-05 15:32:19 +03:00
case st . IsImage ( ) && ( setting . UI . SVG . Enabled || ! st . IsSvgImage ( ) ) :
2016-08-30 12:08:38 +03:00
ctx . Data [ "IsImageFile" ] = true
2019-10-21 09:54:18 +03:00
default :
if fileSize >= setting . UI . MaxDisplayFileSize {
ctx . Data [ "IsFileTooLarge" ] = true
break
}
if markupType := markup . Type ( blob . Name ( ) ) ; markupType != "" {
2021-04-20 01:25:08 +03:00
rd := io . MultiReader ( bytes . NewReader ( buf ) , dataRc )
2019-10-21 09:54:18 +03:00
ctx . Data [ "IsMarkup" ] = true
ctx . Data [ "MarkupType" ] = markupType
2021-04-20 01:25:08 +03:00
var result strings . Builder
err := markup . Render ( & markup . RenderContext {
2021-07-13 00:13:59 +03:00
Ctx : ctx ,
2021-04-20 01:25:08 +03:00
Filename : blob . Name ( ) ,
URLPrefix : path . Dir ( treeLink ) ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ) ,
2021-06-21 01:39:12 +03:00
GitRepo : ctx . Repo . GitRepo ,
2021-04-20 01:25:08 +03:00
} , rd , & result )
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
ctx . Data [ "FileContent" ] = result . String ( )
2019-10-21 09:54:18 +03:00
}
2014-07-26 08:24:27 +04:00
}
2016-08-30 12:08:38 +03:00
if ctx . Repo . CanEnableEditor ( ) {
2019-10-30 00:32:21 +03:00
if lfsLock != nil && lfsLock . OwnerID != ctx . User . ID {
ctx . Data [ "CanDeleteFile" ] = false
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.this_file_locked" )
} else {
ctx . Data [ "CanDeleteFile" ] = true
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.delete_this_file" )
}
2016-08-30 12:08:38 +03:00
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
2018-11-28 14:26:14 +03:00
} else if ! ctx . Repo . CanWrite ( models . UnitTypeCode ) {
2016-08-30 12:08:38 +03:00
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_have_write_access" )
}
}
2019-10-13 16:23:14 +03:00
func safeURL ( address string ) string {
u , err := url . Parse ( address )
if err != nil {
return address
}
u . User = nil
return u . String ( )
}
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 ) {
2018-11-28 14:26:14 +03:00
if len ( ctx . Repo . Units ) > 0 {
2019-10-13 16:23:14 +03:00
if ctx . Repo . Repository . IsBeingCreated ( ) {
task , err := models . GetMigratingTask ( ctx . Repo . Repository . ID )
if err != nil {
ctx . ServerError ( "models.GetMigratingTask" , err )
return
}
cfg , err := task . MigrateConfig ( )
if err != nil {
ctx . ServerError ( "task.MigrateConfig" , err )
return
}
ctx . Data [ "Repo" ] = ctx . Repo
ctx . Data [ "MigrateTask" ] = task
ctx . Data [ "CloneAddr" ] = safeURL ( cfg . CloneAddr )
2021-06-30 22:14:53 +03:00
ctx . Data [ "Failed" ] = task . Status == structs . TaskStatusFailed
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplMigrating )
2019-10-13 16:23:14 +03:00
return
}
2021-03-01 03:47:30 +03:00
if ctx . IsSigned {
// Set repo notification-status read if unread
if err := ctx . Repo . Repository . ReadBy ( ctx . User . ID ) ; err != nil {
ctx . ServerError ( "ReadBy" , err )
return
}
}
2017-10-01 16:50:56 +03:00
var firstUnit * models . Unit
2018-11-28 14:26:14 +03:00
for _ , repoUnit := range ctx . Repo . Units {
2017-10-01 16:50:56 +03:00
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
}
}
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "Home" , fmt . Errorf ( ctx . Tr ( "units.error.no_unit_allowed_repo" ) ) )
2017-05-18 17:54:24 +03:00
}
2020-02-11 12:34:17 +03:00
func renderLanguageStats ( ctx * context . Context ) {
langs , err := ctx . Repo . Repository . GetTopLanguageStats ( 5 )
if err != nil {
ctx . ServerError ( "Repo.GetTopLanguageStats" , err )
return
}
ctx . Data [ "LanguageStats" ] = langs
}
2020-05-06 00:51:49 +03:00
func renderRepoTopics ( ctx * context . Context ) {
topics , err := models . FindTopics ( & models . FindTopicOptions {
RepoID : ctx . Repo . Repository . ID ,
} )
if err != nil {
ctx . ServerError ( "models.FindTopics" , err )
return
}
ctx . Data [ "Topics" ] = topics
}
2017-05-18 17:54:24 +03:00
func renderCode ( ctx * context . Context ) {
2017-03-18 13:59:07 +03:00
ctx . Data [ "PageIsViewCode" ] = true
2019-01-18 03:01:04 +03:00
if ctx . Repo . Repository . IsEmpty {
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplRepoEMPTY )
2017-03-18 13:59:07 +03:00
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
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
}
2018-04-11 05:51:44 +03:00
// Get Topics of this repo
2020-05-06 00:51:49 +03:00
renderRepoTopics ( ctx )
if ctx . Written ( ) {
2018-04-11 05:51:44 +03:00
return
}
2016-08-30 12:08:38 +03:00
// 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
}
2020-02-11 12:34:17 +03:00
renderLanguageStats ( ctx )
if ctx . Written ( ) {
return
}
2016-08-30 12:08:38 +03:00
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
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplRepoHome )
2014-07-26 08:24:27 +04:00
}
2015-11-17 07:28:46 +03:00
2021-07-08 14:38:13 +03:00
// RenderUserCards render a page show users according the input template
2020-01-24 22:00:29 +03:00
func RenderUserCards ( ctx * context . Context , total int , getter func ( opts models . ListOptions ) ( [ ] * models . User , error ) , tpl base . TplName ) {
2021-07-29 04:42:15 +03:00
page := ctx . FormInt ( "page" )
2015-11-17 07:28:46 +03:00
if page <= 0 {
page = 1
}
2019-04-20 07:15:19 +03:00
pager := context . NewPagination ( total , models . ItemsPerPage , page , 5 )
2015-11-17 07:28:46 +03:00
ctx . Data [ "Page" ] = pager
2021-02-04 20:23:46 +03:00
items , err := getter ( models . ListOptions {
Page : pager . Paginater . Current ( ) ,
PageSize : models . ItemsPerPage ,
} )
2015-11-17 07:28:46 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "getter" , err )
2015-11-17 07:28:46 +03:00
return
}
2015-12-21 15:24:11 +03:00
ctx . Data [ "Cards" ] = items
2015-11-17 07:28:46 +03:00
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , 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
2019-04-20 07:15:19 +03:00
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" )
2021-02-04 20:23:46 +03:00
// TODO: need pagination
2020-01-24 22:00:29 +03:00
forks , err := ctx . Repo . Repository . GetForks ( models . ListOptions { } )
2015-11-17 07:33:40 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetForks" , err )
2015-11-17 07:33:40 +03:00
return
}
for _ , fork := range forks {
if err = fork . GetOwner ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetOwner" , err )
2015-11-17 07:33:40 +03:00
return
}
}
ctx . Data [ "Forks" ] = forks
2021-04-05 18:30:52 +03:00
ctx . HTML ( http . StatusOK , tplForks )
2015-11-17 07:33:40 +03:00
}