2017-09-14 08:51:32 +02:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-07-26 06:24:27 +02: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"
2021-10-08 15:08:22 +02:00
gocontext "context"
2017-01-29 21:13:57 +01:00
"encoding/base64"
2016-08-09 21:56:00 +02:00
"fmt"
gotemplate "html/template"
2021-01-22 18:49:13 +01:00
"io"
2021-04-05 17:30:52 +02:00
"net/http"
2019-10-13 15:23:14 +02:00
"net/url"
2014-07-26 06:24:27 +02:00
"path"
"strings"
2021-10-08 15:08:22 +02:00
"time"
2014-07-26 06:24:27 +02:00
2022-08-25 04:31:57 +02:00
activities_model "code.gitea.io/gitea/models/activities"
admin_model "code.gitea.io/gitea/models/admin"
2021-12-10 09:14:24 +01:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
2021-09-24 13:32:56 +02:00
"code.gitea.io/gitea/models/db"
2022-06-12 17:51:54 +02:00
git_model "code.gitea.io/gitea/models/git"
2021-12-10 02:27:50 +01:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 20:57:58 +01:00
unit_model "code.gitea.io/gitea/models/unit"
2021-11-24 10:49:20 +01:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/base"
2019-08-15 14:07:28 +02:00
"code.gitea.io/gitea/modules/charset"
2022-10-12 07:18:26 +02:00
"code.gitea.io/gitea/modules/container"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/context"
2019-03-27 10:33:00 +01:00
"code.gitea.io/gitea/modules/git"
2016-12-06 18:58:31 +01:00
"code.gitea.io/gitea/modules/highlight"
2016-12-26 02:16:37 +01:00
"code.gitea.io/gitea/modules/lfs"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/log"
2017-04-21 09:01:08 +02:00
"code.gitea.io/gitea/modules/markup"
2022-06-06 10:01:49 +02:00
repo_module "code.gitea.io/gitea/modules/repository"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2021-06-30 21:14:53 +02:00
"code.gitea.io/gitea/modules/structs"
2021-06-05 14:32:19 +02:00
"code.gitea.io/gitea/modules/typesniffer"
2021-10-24 23:12:43 +02:00
"code.gitea.io/gitea/modules/util"
2022-03-13 17:40:47 +01:00
"code.gitea.io/gitea/routers/web/feed"
2014-07-26 06:24:27 +02:00
)
const (
2021-10-08 15:08:22 +02:00
tplRepoEMPTY base . TplName = "repo/empty"
tplRepoHome base . TplName = "repo/home"
tplRepoViewList base . TplName = "repo/view_list"
tplWatchers base . TplName = "repo/watchers"
tplForks base . TplName = "repo/forks"
tplMigrating base . TplName = "repo/migrate/migrating"
2014-07-26 06:24:27 +02:00
)
2020-02-22 00:04:20 +01:00
type namedBlob struct {
name string
isSymlink bool
blob * git . Blob
}
// FIXME: There has to be a more efficient way of doing this
2022-08-01 00:36:58 +02:00
func getReadmeFileFromPath ( ctx * context . Context , commit * git . Commit , treePath string ) ( * namedBlob , error ) {
2020-02-22 00:04:20 +01:00
tree , err := commit . SubTree ( treePath )
if err != nil {
return nil , err
}
entries , err := tree . ListEntries ( )
if err != nil {
return nil , err
}
2022-08-01 00:36:58 +02:00
// Create a list of extensions in priority order
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
// 2. Txt files - e.g. README.txt
// 3. No extension - e.g. README
exts := append ( localizedExtensions ( ".md" , ctx . Language ( ) ) , ".txt" , "" ) // sorted by priority
extCount := len ( exts )
readmeFiles := make ( [ ] * namedBlob , extCount + 1 )
2020-02-22 00:04:20 +01:00
for _ , entry := range entries {
if entry . IsDir ( ) {
continue
}
2022-08-01 00:36:58 +02:00
if i , ok := markup . IsReadmeFileExtension ( entry . Name ( ) , exts ... ) ; ok {
if readmeFiles [ i ] == nil || base . NaturalSortLess ( readmeFiles [ i ] . name , entry . Blob ( ) . Name ( ) ) {
2020-02-22 00:04:20 +01:00
name := entry . Name ( )
isSymlink := entry . IsLink ( )
2022-08-01 00:36:58 +02:00
target := entry
2020-02-22 00:04:20 +01:00
if isSymlink {
2022-08-01 00:36:58 +02:00
target , err = entry . FollowLinks ( )
2020-02-22 00:04:20 +01:00
if err != nil && ! git . IsErrBadLink ( err ) {
return nil , err
}
}
2022-08-01 00:36:58 +02:00
if target != nil && ( target . IsExecutable ( ) || target . IsRegular ( ) ) {
readmeFiles [ i ] = & namedBlob {
2020-02-22 00:04:20 +01:00
name ,
isSymlink ,
2022-08-01 00:36:58 +02:00
target . Blob ( ) ,
2020-02-22 00:04:20 +01:00
}
}
}
}
}
var readmeFile * namedBlob
for _ , f := range readmeFiles {
if f != nil {
readmeFile = f
break
}
}
return readmeFile , nil
}
2016-08-30 11:08:38 +02:00
func renderDirectory ( ctx * context . Context , treeLink string ) {
2021-10-08 15:08:22 +02:00
entries := renderDirectoryFiles ( ctx , 1 * time . Second )
if ctx . Written ( ) {
2014-07-26 06:24:27 +02:00
return
}
2021-12-15 08:50:11 +01:00
if ctx . Repo . TreePath != "" {
2022-10-13 10:31:10 +02:00
ctx . Data [ "HideRepoInfo" ] = true
2021-12-15 08:50:11 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.file.title" , ctx . Repo . Repository . Name + "/" + path . Base ( ctx . Repo . TreePath ) , ctx . Repo . RefName )
}
2022-04-26 22:31:15 +02:00
// Check permission to add or upload new file.
if ctx . Repo . CanWrite ( unit_model . TypeCode ) && ctx . Repo . IsViewBranch {
ctx . Data [ "CanAddFile" ] = ! ctx . Repo . Repository . IsArchived
ctx . Data [ "CanUploadFile" ] = setting . Repository . Upload . Enabled && ! ctx . Repo . Repository . IsArchived
}
readmeFile , readmeTreelink := findReadmeFile ( ctx , entries , treeLink )
if ctx . Written ( ) || readmeFile == nil {
return
}
renderReadmeFile ( ctx , readmeFile , readmeTreelink )
}
2022-08-01 00:36:58 +02:00
// localizedExtensions prepends the provided language code with and without a
Fix various typos (#21103)
Found via `codespell -q 3 -S
./options/locale,./options/license,./public/vendor,./web_src/fomantic -L
actived,allways,attachements,ba,befores,commiter,pullrequest,pullrequests,readby,splitted,te,unknwon`
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2022-09-07 20:40:36 +02:00
// regional identifier to the provided extension.
2022-08-01 00:36:58 +02:00
// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
// Note: ext should be prefixed with a `.`
func localizedExtensions ( ext , languageCode string ) ( localizedExts [ ] string ) {
if len ( languageCode ) < 1 {
return [ ] string { ext }
}
lowerLangCode := "." + strings . ToLower ( languageCode )
if strings . Contains ( lowerLangCode , "-" ) {
underscoreLangCode := strings . ReplaceAll ( lowerLangCode , "-" , "_" )
indexOfDash := strings . Index ( lowerLangCode , "-" )
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, .md]
return [ ] string { lowerLangCode + ext , underscoreLangCode + ext , lowerLangCode [ : indexOfDash ] + ext , ext }
}
// e.g. [.en.md, .md]
return [ ] string { lowerLangCode + ext , ext }
}
2022-04-26 22:31:15 +02:00
func findReadmeFile ( ctx * context . Context , entries git . Entries , treeLink string ) ( * namedBlob , string ) {
2022-08-01 00:36:58 +02:00
// Create a list of extensions in priority order
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
// 2. Txt files - e.g. README.txt
// 3. No extension - e.g. README
exts := append ( localizedExtensions ( ".md" , ctx . Language ( ) ) , ".txt" , "" ) // sorted by priority
extCount := len ( exts )
readmeFiles := make ( [ ] * namedBlob , extCount + 1 )
docsEntries := make ( [ ] * git . TreeEntry , 3 ) // (one of docs/, .gitea/ or .github/)
2016-08-30 11:08:38 +02:00
for _ , entry := range entries {
2017-05-02 10:57:54 +02:00
if entry . IsDir ( ) {
2020-02-22 00:04:20 +01: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 10:57:54 +02:00
continue
}
2022-08-01 00:36:58 +02:00
if i , ok := markup . IsReadmeFileExtension ( entry . Name ( ) , exts ... ) ; ok {
log . Debug ( "Potential readme file: %s" , entry . Name ( ) )
2020-02-22 00:04:20 +01:00
name := entry . Name ( )
isSymlink := entry . IsLink ( )
2022-08-01 00:36:58 +02:00
target := entry
2020-02-22 00:04:20 +01:00
if isSymlink {
2021-10-08 15:08:22 +02:00
var err error
2022-08-01 00:36:58 +02:00
target , err = entry . FollowLinks ( )
2020-02-22 00:04:20 +01:00
if err != nil && ! git . IsErrBadLink ( err ) {
ctx . ServerError ( "FollowLinks" , err )
2022-04-26 22:31:15 +02:00
return nil , ""
2020-02-22 00:04:20 +01:00
}
}
2022-08-01 00:36:58 +02:00
if target != nil && ( target . IsExecutable ( ) || target . IsRegular ( ) ) {
readmeFiles [ i ] = & namedBlob {
2020-02-22 00:04:20 +01:00
name ,
isSymlink ,
2022-08-01 00:36:58 +02:00
target . Blob ( ) ,
2020-02-22 00:04:20 +01:00
}
}
2016-08-30 11:08:38 +02:00
}
2019-01-14 20:15:06 +01:00
}
2016-08-30 11:08:38 +02:00
2020-02-22 00:04:20 +01:00
var readmeFile * namedBlob
readmeTreelink := treeLink
2019-01-14 20:15:06 +01:00
for _ , f := range readmeFiles {
if f != nil {
readmeFile = f
2017-05-02 10:57:54 +02:00
break
}
2016-08-30 11:08:38 +02:00
}
2020-02-22 00:04:20 +01:00
if ctx . Repo . TreePath == "" && readmeFile == nil {
for _ , entry := range docsEntries {
if entry == nil {
continue
}
2021-10-08 15:08:22 +02:00
var err error
2022-08-01 00:36:58 +02:00
readmeFile , err = getReadmeFileFromPath ( ctx , ctx . Repo . Commit , entry . GetSubJumpablePathName ( ) )
2020-02-22 00:04:20 +01:00
if err != nil {
ctx . ServerError ( "getReadmeFileFromPath" , err )
2022-04-26 22:31:15 +02:00
return nil , ""
2020-02-22 00:04:20 +01:00
}
if readmeFile != nil {
readmeFile . name = entry . Name ( ) + "/" + readmeFile . name
2021-11-16 19:18:25 +01:00
readmeTreelink = treeLink + "/" + util . PathEscapeSegments ( entry . GetSubJumpablePathName ( ) )
2020-02-22 00:04:20 +01:00
break
}
}
}
2022-04-26 22:31:15 +02:00
return readmeFile , readmeTreelink
}
2020-02-22 00:04:20 +01:00
2022-04-26 22:31:15 +02:00
func renderReadmeFile ( ctx * context . Context , readmeFile * namedBlob , readmeTreelink string ) {
ctx . Data [ "RawFileLink" ] = ""
ctx . Data [ "ReadmeInList" ] = true
ctx . Data [ "ReadmeExist" ] = true
ctx . Data [ "FileIsSymlink" ] = readmeFile . isSymlink
2016-08-30 11:08:38 +02:00
2022-04-26 22:31:15 +02:00
dataRc , err := readmeFile . blob . DataAsync ( )
if err != nil {
ctx . ServerError ( "Data" , err )
return
}
defer dataRc . Close ( )
buf := make ( [ ] byte , 1024 )
n , _ := util . ReadAtMost ( dataRc , buf )
buf = buf [ : n ]
st := typesniffer . DetectContentType ( buf )
isTextFile := st . IsText ( )
ctx . Data [ "FileIsText" ] = isTextFile
ctx . Data [ "FileName" ] = readmeFile . name
fileSize := int64 ( 0 )
isLFSFile := false
ctx . Data [ "IsLFSFile" ] = false
// FIXME: what happens when README file is an image?
if isTextFile && setting . LFS . StartServer {
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
if pointer . IsValid ( ) {
2022-06-12 17:51:54 +02:00
meta , err := git_model . GetLFSMetaObjectByOid ( ctx . Repo . Repository . ID , pointer . Oid )
if err != nil && err != git_model . ErrLFSObjectNotExist {
2022-04-26 22:31:15 +02:00
ctx . ServerError ( "GetLFSMetaObject" , err )
return
}
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 )
2019-02-21 21:57:16 +01:00
return
}
2022-04-26 22:31:15 +02:00
defer dataRc . Close ( )
2019-02-21 21:57:16 +01:00
2022-04-26 22:31:15 +02:00
buf = make ( [ ] byte , 1024 )
n , err = util . ReadAtMost ( dataRc , buf )
if err != nil {
ctx . ServerError ( "Data" , err )
return
}
buf = buf [ : n ]
2019-02-21 21:57:16 +01:00
2022-04-26 22:31:15 +02:00
st = typesniffer . DetectContentType ( buf )
isTextFile = st . IsText ( )
ctx . Data [ "IsTextFile" ] = isTextFile
2019-02-21 21:57:16 +01:00
2022-04-26 22:31:15 +02:00
fileSize = meta . Size
ctx . Data [ "FileSize" ] = meta . Size
filenameBase64 := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( readmeFile . name ) )
ctx . Data [ "RawFileLink" ] = fmt . Sprintf ( "%s.git/info/lfs/objects/%s/%s" , ctx . Repo . Repository . HTMLURL ( ) , url . PathEscape ( meta . Oid ) , url . PathEscape ( filenameBase64 ) )
2019-02-21 21:57:16 +01:00
}
}
2022-04-26 22:31:15 +02:00
}
2019-02-21 21:57:16 +01:00
2022-04-26 22:31:15 +02:00
if ! isTextFile {
return
}
2019-02-21 21:57:16 +01:00
2022-04-26 22:31:15 +02:00
if ! isLFSFile {
fileSize = readmeFile . blob . Size ( )
}
2022-01-07 02:18:52 +01:00
2022-04-26 22:31:15 +02:00
if fileSize >= 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" ] = fileSize
return
2016-08-30 11:08:38 +02:00
}
2016-08-15 08:02:14 +02:00
2022-04-26 22:31:15 +02:00
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) )
if markupType := markup . Type ( readmeFile . name ) ; markupType != "" {
ctx . Data [ "IsMarkup" ] = true
2022-06-20 12:02:49 +02:00
ctx . Data [ "MarkupType" ] = markupType
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 05:33:23 +02:00
Ctx : ctx ,
2022-06-17 07:48:10 +02:00
RelativePath : path . Join ( ctx . Repo . TreePath , readmeFile . name ) , // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
2022-06-16 05:33:23 +02:00
URLPrefix : readmeTreelink ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ) ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-13 20:32:34 +02:00
} , rd )
2022-04-26 22:31:15 +02:00
if err != nil {
2022-08-13 20:32:34 +02:00
log . Error ( "Render failed for %s in %-v: %v Falling back to rendering source" , readmeFile . name , ctx . Repo . Repository , err )
2022-04-26 22:31:15 +02:00
buf := & bytes . Buffer { }
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , _ = charset . EscapeControlReader ( rd , buf , ctx . Locale )
2022-04-26 22:31:15 +02:00
ctx . Data [ "FileContent" ] = strings . ReplaceAll (
gotemplate . HTMLEscapeString ( buf . String ( ) ) , "\n" , ` <br> ` ,
)
}
} else {
ctx . Data [ "IsRenderedHTML" ] = true
buf := & bytes . Buffer { }
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , err = charset . EscapeControlReader ( rd , & charset . BreakWriter { Writer : buf } , ctx . Locale , charset . RuneNBSP )
2022-04-26 22:31:15 +02:00
if err != nil {
log . Error ( "Read failed: %v" , err )
}
2022-08-13 20:32:34 +02:00
ctx . Data [ "FileContent" ] = buf . String ( )
2016-08-30 11:08:38 +02:00
}
}
2014-09-30 10:39:53 +02:00
2016-08-30 11:08:38 +02:00
func renderFile ( ctx * context . Context , entry * git . TreeEntry , treeLink , rawLink string ) {
ctx . Data [ "IsViewFile" ] = true
2022-10-13 10:31:10 +02:00
ctx . Data [ "HideRepoInfo" ] = true
2016-08-30 11:08:38 +02:00
blob := entry . Blob ( )
2017-11-29 02:50:39 +01:00
dataRc , err := blob . DataAsync ( )
2016-08-30 11:08:38 +02:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx . ServerError ( "DataAsync" , err )
2016-08-30 11:08:38 +02:00
return
}
2017-11-29 02:50:39 +01:00
defer dataRc . Close ( )
2014-07-26 06:24:27 +02:00
2021-12-15 08:50:11 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.file.title" , ctx . Repo . Repository . Name + "/" + path . Base ( ctx . Repo . TreePath ) , ctx . Repo . RefName )
2018-09-10 19:01:49 +02:00
2019-02-21 21:57:16 +01:00
fileSize := blob . Size ( )
2020-02-22 00:04:20 +01:00
ctx . Data [ "FileIsSymlink" ] = entry . IsLink ( )
2016-08-30 11:08:38 +02:00
ctx . Data [ "FileName" ] = blob . Name ( )
2021-11-16 19:18:25 +01:00
ctx . Data [ "RawFileLink" ] = rawLink + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2016-08-30 11:08:38 +02:00
2022-09-12 00:16:56 +02:00
if ctx . Repo . TreePath == ".editorconfig" {
2022-09-14 20:54:30 +02:00
_ , editorconfigErr := ctx . Repo . GetEditorconfig ( ctx . Repo . Commit )
2022-09-12 00:16:56 +02:00
ctx . Data [ "FileError" ] = editorconfigErr
}
2016-08-30 11:08:38 +02:00
buf := make ( [ ] byte , 1024 )
2021-10-24 23:12:43 +02:00
n , _ := util . ReadAtMost ( dataRc , buf )
2016-08-31 22:59:23 +02:00
buf = buf [ : n ]
2016-08-30 11:08:38 +02:00
2021-06-05 14:32:19 +02:00
st := typesniffer . DetectContentType ( buf )
isTextFile := st . IsText ( )
2019-02-12 16:09:43 +01:00
isLFSFile := false
2021-08-11 02:31:13 +02:00
isDisplayingSource := ctx . FormString ( "display" ) == "source"
2021-01-13 04:45:19 +01:00
isDisplayingRendered := ! isDisplayingSource
2022-01-20 18:46:10 +01:00
// Check for LFS meta file
2019-02-21 21:57:16 +01:00
if isTextFile && setting . LFS . StartServer {
2021-04-09 00:25:57 +02:00
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
if pointer . IsValid ( ) {
2022-06-12 17:51:54 +02:00
meta , err := git_model . GetLFSMetaObjectByOid ( ctx . Repo . Repository . ID , pointer . Oid )
if err != nil && err != git_model . ErrLFSObjectNotExist {
2019-02-21 21:57:16 +01:00
ctx . ServerError ( "GetLFSMetaObject" , err )
return
}
2021-04-09 00:25:57 +02:00
if meta != nil {
isLFSFile = true
2019-02-21 21:57:16 +01:00
2021-04-09 00:25:57 +02: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 )
2021-10-24 23:12:43 +02:00
n , err = util . ReadAtMost ( dataRc , buf )
if err != nil {
2021-04-09 00:25:57 +02:00
ctx . ServerError ( "Data" , err )
return
}
buf = buf [ : n ]
2019-02-12 16:09:43 +01:00
2021-06-05 14:32:19 +02:00
st = typesniffer . DetectContentType ( buf )
isTextFile = st . IsText ( )
2021-04-09 00:25:57 +02:00
fileSize = meta . Size
2021-11-16 19:18:25 +01:00
ctx . Data [ "RawFileLink" ] = ctx . Repo . RepoLink + "/media/" + ctx . Repo . BranchNameSubURL ( ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2021-04-09 00:25:57 +02:00
}
2016-12-26 02:16:37 +01:00
}
}
2021-02-05 02:29:42 +01:00
2021-06-05 14:32:19 +02:00
isRepresentableAsText := st . IsRepresentableAsText ( )
2021-02-05 02:29:42 +01: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-29 22:32:21 +01:00
// Check LFS Lock
2022-06-12 17:51:54 +02:00
lfsLock , err := git_model . GetTreePathLock ( ctx . Repo . Repository . ID , ctx . Repo . TreePath )
2019-10-29 22:32:21 +01:00
ctx . Data [ "LFSLock" ] = lfsLock
if err != nil {
ctx . ServerError ( "GetTreePathLock" , err )
return
}
if lfsLock != nil {
2021-11-24 10:49:20 +01:00
u , err := user_model . GetUserByID ( lfsLock . OwnerID )
if err != nil {
ctx . ServerError ( "GetTreePathLock" , err )
return
}
2022-10-12 03:03:15 +02:00
ctx . Data [ "LFSLockOwner" ] = u . Name
2022-02-16 17:22:25 +01:00
ctx . Data [ "LFSLockOwnerHomeLink" ] = u . HomeLink ( )
2019-10-29 22:32:21 +01:00
ctx . Data [ "LFSLockHint" ] = ctx . Tr ( "repo.editor.this_file_locked" )
}
2016-12-26 02:16:37 +01:00
2016-08-30 11:08:38 +02:00
// Assume file is not editable first.
2019-02-12 16:09:43 +01:00
if isLFSFile {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_lfs_files" )
2021-01-13 04:45:19 +01:00
} else if ! isRepresentableAsText {
2016-08-30 11:08:38 +02:00
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_non_text_files" )
}
switch {
2021-01-13 04:45:19 +01:00
case isRepresentableAsText :
2021-06-05 14:32:19 +02:00
if st . IsSvgImage ( ) {
2021-01-13 04:45:19 +01:00
ctx . Data [ "IsImageFile" ] = true
ctx . Data [ "HasSourceRenderedToggle" ] = true
}
2019-02-21 21:57:16 +01:00
if fileSize >= setting . UI . MaxDisplayFileSize {
2016-08-30 11:08:38 +02:00
ctx . Data [ "IsFileTooLarge" ] = true
break
2014-07-26 06:24:27 +02:00
}
2021-04-20 00:25:08 +02:00
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) )
2022-04-10 17:01:35 +02:00
shouldRenderSource := ctx . FormString ( "display" ) == "source"
2017-09-21 07:20:14 +02:00
readmeExist := markup . IsReadmeFile ( blob . Name ( ) )
2016-08-30 11:08:38 +02:00
ctx . Data [ "ReadmeExist" ] = readmeExist
2022-04-10 17:01:35 +02:00
markupType := markup . Type ( blob . Name ( ) )
2022-06-08 23:46:39 +02:00
// If the markup is detected by custom markup renderer it should not be reset later on
// to not pass it down to the render context.
detected := false
if markupType == "" {
detected = true
markupType = markup . DetectRendererType ( blob . Name ( ) , bytes . NewReader ( buf ) )
}
2022-04-10 17:01:35 +02:00
if markupType != "" {
ctx . Data [ "HasSourceRenderedToggle" ] = true
}
if markupType != "" && ! shouldRenderSource {
2017-10-17 01:17:22 +02:00
ctx . Data [ "IsMarkup" ] = true
2019-08-16 00:09:50 +02:00
ctx . Data [ "MarkupType" ] = markupType
2022-06-08 23:46:39 +02:00
if ! detected {
markupType = ""
}
2022-06-16 05:33:23 +02:00
metas := ctx . Repo . Repository . ComposeDocumentMetas ( )
metas [ "BranchNameSubURL" ] = ctx . Repo . BranchNameSubURL ( )
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 05:33:23 +02:00
Ctx : ctx ,
Type : markupType ,
RelativePath : ctx . Repo . TreePath ,
URLPrefix : path . Dir ( treeLink ) ,
Metas : metas ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-13 20:32:34 +02:00
} , rd )
2021-04-20 00:25:08 +02:00
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
2022-06-16 05:33:23 +02:00
// to prevent iframe load third-party url
ctx . Resp . Header ( ) . Add ( "Content-Security-Policy" , "frame-src 'self'" )
2022-04-10 17:01:35 +02:00
} else if readmeExist && ! shouldRenderSource {
2022-01-07 02:18:52 +01:00
buf := & bytes . Buffer { }
2017-10-16 09:04:34 +02:00
ctx . Data [ "IsRenderedHTML" ] = true
2022-01-07 02:18:52 +01:00
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , _ = charset . EscapeControlReader ( rd , buf , ctx . Locale )
2022-01-07 02:18:52 +01:00
2020-10-11 22:27:20 +02:00
ctx . Data [ "FileContent" ] = strings . ReplaceAll (
2022-01-07 02:18:52 +01:00
gotemplate . HTMLEscapeString ( buf . String ( ) ) , "\n" , ` <br> ` ,
2018-06-10 20:42:16 +02:00
)
2016-08-30 11:08:38 +02:00
} else {
2021-09-22 07:38:34 +02:00
buf , _ := io . ReadAll ( rd )
2022-07-30 21:17:43 +02:00
// empty: 0 lines; "a": one line; "a\n": two lines; "a\nb": two lines;
// the NumLines is only used for the display on the UI: "xxx lines"
if len ( buf ) == 0 {
ctx . Data [ "NumLines" ] = 0
} else {
ctx . Data [ "NumLines" ] = bytes . Count ( buf , [ ] byte { '\n' } ) + 1
}
2019-10-29 17:05:26 +01:00
ctx . Data [ "NumLinesSet" ] = true
2021-11-17 21:37:00 +01:00
language := ""
indexFilename , worktree , deleteTemporaryFile , err := ctx . Repo . GitRepo . ReadTreeToTemporaryIndex ( ctx . Repo . CommitID )
if err == nil {
defer deleteTemporaryFile ( )
filename2attribute2info , err := ctx . Repo . GitRepo . CheckAttribute ( git . CheckAttributeOpts {
CachedOnly : true ,
2022-10-23 16:44:45 +02:00
Attributes : [ ] git . CmdArg { "linguist-language" , "gitlab-language" } ,
2021-11-17 21:37:00 +01:00
Filenames : [ ] string { ctx . Repo . TreePath } ,
IndexFile : indexFilename ,
WorkTree : worktree ,
} )
if err != nil {
log . Error ( "Unable to load attributes for %-v:%s. Error: %v" , ctx . Repo . Repository , ctx . Repo . TreePath , err )
}
language = filename2attribute2info [ ctx . Repo . TreePath ] [ "linguist-language" ]
if language == "" || language == "unspecified" {
language = filename2attribute2info [ ctx . Repo . TreePath ] [ "gitlab-language" ]
}
if language == "unspecified" {
language = ""
}
}
2022-07-30 21:17:43 +02:00
fileContent , err := highlight . File ( blob . Name ( ) , language , buf )
if err != nil {
log . Error ( "highlight.File failed, fallback to plain text: %v" , err )
fileContent = highlight . PlainText ( buf )
}
2022-08-13 20:32:34 +02:00
status := & charset . EscapeStatus { }
statuses := make ( [ ] * charset . EscapeStatus , len ( fileContent ) )
2022-01-07 02:18:52 +01:00
for i , line := range fileContent {
2022-08-13 20:32:34 +02:00
statuses [ i ] , fileContent [ i ] = charset . EscapeControlHTML ( line , ctx . Locale )
status = status . Or ( statuses [ i ] )
2022-01-07 02:18:52 +01:00
}
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] = status
2022-01-07 02:18:52 +01:00
ctx . Data [ "FileContent" ] = fileContent
ctx . Data [ "LineEscapeStatus" ] = statuses
2014-09-26 14:55:13 +02:00
}
2019-02-12 16:09:43 +01:00
if ! isLFSFile {
2022-04-28 17:45:33 +02:00
if ctx . Repo . CanEnableEditor ( ctx . Doer ) {
2022-03-22 08:03:22 +01:00
if lfsLock != nil && lfsLock . OwnerID != ctx . Doer . ID {
2019-10-29 22:32:21 +01:00
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 16:09:43 +01:00
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
2022-04-28 17:45:33 +02:00
} else if ! ctx . Repo . CanWriteToBranch ( ctx . Doer , ctx . Repo . BranchName ) {
2019-02-12 16:09:43 +01:00
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.fork_before_edit" )
}
2016-08-28 10:41:44 +02:00
}
2016-08-30 11:08:38 +02:00
2021-06-05 14:32:19 +02:00
case st . IsPDF ( ) :
2016-08-30 11:08:38 +02:00
ctx . Data [ "IsPDFFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsVideo ( ) :
2016-12-20 09:09:11 +01:00
ctx . Data [ "IsVideoFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsAudio ( ) :
2018-10-30 03:17:26 +01:00
ctx . Data [ "IsAudioFile" ] = true
2021-06-05 14:32:19 +02:00
case st . IsImage ( ) && ( setting . UI . SVG . Enabled || ! st . IsSvgImage ( ) ) :
2016-08-30 11:08:38 +02:00
ctx . Data [ "IsImageFile" ] = true
2019-10-21 08:54:18 +02:00
default :
if fileSize >= setting . UI . MaxDisplayFileSize {
ctx . Data [ "IsFileTooLarge" ] = true
break
}
if markupType := markup . Type ( blob . Name ( ) ) ; markupType != "" {
2021-04-20 00:25:08 +02:00
rd := io . MultiReader ( bytes . NewReader ( buf ) , dataRc )
2019-10-21 08:54:18 +02:00
ctx . Data [ "IsMarkup" ] = true
ctx . Data [ "MarkupType" ] = markupType
2022-08-13 20:32:34 +02:00
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 05:33:23 +02:00
Ctx : ctx ,
RelativePath : ctx . Repo . TreePath ,
URLPrefix : path . Dir ( treeLink ) ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ) ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-13 20:32:34 +02:00
} , rd )
2021-04-20 00:25:08 +02:00
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
2019-10-21 08:54:18 +02:00
}
2014-07-26 06:24:27 +02:00
}
2022-04-28 17:45:33 +02:00
if ctx . Repo . CanEnableEditor ( ctx . Doer ) {
2022-03-22 08:03:22 +01:00
if lfsLock != nil && lfsLock . OwnerID != ctx . Doer . ID {
2019-10-29 22:32:21 +01:00
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 11:08:38 +02:00
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
2022-04-28 17:45:33 +02:00
} else if ! ctx . Repo . CanWriteToBranch ( ctx . Doer , ctx . Repo . BranchName ) {
2016-08-30 11:08:38 +02:00
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_have_write_access" )
}
}
2022-08-13 20:32:34 +02:00
func markupRender ( ctx * context . Context , renderCtx * markup . RenderContext , input io . Reader ) ( escaped * charset . EscapeStatus , output string , err error ) {
markupRd , markupWr := io . Pipe ( )
defer markupWr . Close ( )
done := make ( chan struct { } )
go func ( ) {
sb := & strings . Builder { }
// We allow NBSP here this is rendered
escaped , _ = charset . EscapeControlReader ( markupRd , sb , ctx . Locale , charset . RuneNBSP )
output = sb . String ( )
close ( done )
} ( )
err = markup . Render ( renderCtx , input , markupWr )
_ = markupWr . CloseWithError ( err )
<- done
return escaped , output , err
}
2019-10-13 15:23:14 +02:00
func safeURL ( address string ) string {
u , err := url . Parse ( address )
if err != nil {
return address
}
u . User = nil
return u . String ( )
}
2021-10-08 15:08:22 +02:00
func checkHomeCodeViewable ( ctx * context . Context ) {
2018-11-28 12:26:14 +01:00
if len ( ctx . Repo . Units ) > 0 {
2019-10-13 15:23:14 +02:00
if ctx . Repo . Repository . IsBeingCreated ( ) {
2022-08-25 04:31:57 +02:00
task , err := admin_model . GetMigratingTask ( ctx . Repo . Repository . ID )
2019-10-13 15:23:14 +02:00
if err != nil {
2022-08-25 04:31:57 +02:00
if admin_model . IsErrTaskDoesNotExist ( err ) {
2021-11-13 12:28:50 +01:00
ctx . Data [ "Repo" ] = ctx . Repo
ctx . Data [ "CloneAddr" ] = ""
ctx . Data [ "Failed" ] = true
ctx . HTML ( http . StatusOK , tplMigrating )
return
}
2019-10-13 15:23:14 +02:00
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 21:14:53 +02:00
ctx . Data [ "Failed" ] = task . Status == structs . TaskStatusFailed
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMigrating )
2019-10-13 15:23:14 +02:00
return
}
2021-03-01 01:47:30 +01:00
if ctx . IsSigned {
// Set repo notification-status read if unread
2022-08-25 04:31:57 +02:00
if err := activities_model . SetRepoReadBy ( ctx , ctx . Repo . Repository . ID , ctx . Doer . ID ) ; err != nil {
2021-03-01 01:47:30 +01:00
ctx . ServerError ( "ReadBy" , err )
return
}
}
2021-11-09 20:57:58 +01:00
var firstUnit * unit_model . Unit
2018-11-28 12:26:14 +01:00
for _ , repoUnit := range ctx . Repo . Units {
2021-11-09 20:57:58 +01:00
if repoUnit . Type == unit_model . TypeCode {
2017-10-01 15:50:56 +02:00
return
}
2021-11-09 20:57:58 +01:00
unit , ok := unit_model . Units [ repoUnit . Type ]
2017-10-01 15:50:56 +02:00
if ok && ( firstUnit == nil || ! firstUnit . IsLessThan ( unit ) ) {
firstUnit = & unit
}
2017-05-18 16:54:24 +02:00
}
2017-10-01 15:50:56 +02:00
if firstUnit != nil {
2021-11-16 19:18:25 +01:00
ctx . Redirect ( fmt . Sprintf ( "%s%s" , ctx . Repo . Repository . Link ( ) , firstUnit . URI ) )
2017-05-18 16:54:24 +02:00
return
}
}
2018-01-10 22:34:17 +01:00
ctx . NotFound ( "Home" , fmt . Errorf ( ctx . Tr ( "units.error.no_unit_allowed_repo" ) ) )
2017-05-18 16:54:24 +02:00
}
2021-10-08 15:08:22 +02:00
// Home render repository home page
func Home ( ctx * context . Context ) {
2022-03-13 17:40:47 +01:00
isFeed , _ , showFeedType := feed . GetFeedType ( ctx . Params ( ":reponame" ) , ctx . Req )
if isFeed {
feed . ShowRepoFeed ( ctx , ctx . Repo . Repository , showFeedType )
return
}
ctx . Data [ "FeedURL" ] = ctx . Repo . Repository . HTMLURL ( )
2021-10-08 15:08:22 +02:00
checkHomeCodeViewable ( ctx )
if ctx . Written ( ) {
return
}
renderCode ( ctx )
}
// LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body
func LastCommit ( ctx * context . Context ) {
checkHomeCodeViewable ( ctx )
if ctx . Written ( ) {
return
}
renderDirectoryFiles ( ctx , 0 )
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 ]
}
}
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
ctx . Data [ "BranchLink" ] = branchLink
ctx . HTML ( http . StatusOK , tplRepoViewList )
}
func renderDirectoryFiles ( ctx * context . Context , timeout time . Duration ) git . Entries {
tree , err := ctx . Repo . Commit . SubTree ( ctx . Repo . TreePath )
if err != nil {
ctx . NotFoundOrServerError ( "Repo.Commit.SubTree" , git . IsErrNotExist , err )
return nil
}
2021-11-16 19:18:25 +01:00
ctx . Data [ "LastCommitLoaderURL" ] = ctx . Repo . RepoLink + "/lastcommit/" + url . PathEscape ( ctx . Repo . CommitID ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2021-10-08 15:08:22 +02: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 nil
}
if ! entry . IsDir ( ) {
ctx . NotFoundOrServerError ( "Repo.Commit.GetTreeEntryByPath" , git . IsErrNotExist , err )
return nil
}
allEntries , err := tree . ListEntries ( )
if err != nil {
ctx . ServerError ( "ListEntries" , err )
return nil
}
allEntries . CustomSort ( base . NaturalSortLess )
commitInfoCtx := gocontext . Context ( ctx )
if timeout > 0 {
var cancel gocontext . CancelFunc
commitInfoCtx , cancel = gocontext . WithTimeout ( ctx , timeout )
defer cancel ( )
}
2022-10-12 07:18:26 +02:00
selected := make ( container . Set [ string ] )
selected . AddMultiple ( ctx . FormStrings ( "f[]" ) ... )
2021-10-08 15:08:22 +02:00
entries := allEntries
if len ( selected ) > 0 {
entries = make ( git . Entries , 0 , len ( selected ) )
for _ , entry := range allEntries {
2022-10-12 07:18:26 +02:00
if selected . Contains ( entry . Name ( ) ) {
2021-10-08 15:08:22 +02:00
entries = append ( entries , entry )
}
}
}
var latestCommit * git . Commit
2022-07-25 17:39:42 +02:00
ctx . Data [ "Files" ] , latestCommit , err = entries . GetCommitsInfo ( commitInfoCtx , ctx . Repo . Commit , ctx . Repo . TreePath )
2021-10-08 15:08:22 +02:00
if err != nil {
ctx . ServerError ( "GetCommitsInfo" , err )
return nil
}
// Show latest commit info of repository in table header,
// or of directory if not in root directory.
ctx . Data [ "LatestCommit" ] = latestCommit
if latestCommit != nil {
2021-12-10 09:14:24 +01:00
verification := asymkey_model . ParseCommitWithSignature ( latestCommit )
2021-10-08 15:08:22 +02:00
2021-12-10 09:14:24 +01:00
if err := asymkey_model . CalculateTrustStatus ( verification , ctx . Repo . Repository . GetTrustModel ( ) , func ( user * user_model . User ) ( bool , error ) {
2022-06-12 17:51:54 +02:00
return repo_model . IsOwnerMemberCollaborator ( ctx . Repo . Repository , user . ID )
2021-12-10 09:14:24 +01:00
} , nil ) ; err != nil {
2021-10-08 15:08:22 +02:00
ctx . ServerError ( "CalculateTrustStatus" , err )
return nil
}
ctx . Data [ "LatestCommitVerification" ] = verification
2021-11-24 10:49:20 +01:00
ctx . Data [ "LatestCommitUser" ] = user_model . ValidateCommitWithEmail ( latestCommit )
2021-10-08 15:08:22 +02:00
2022-07-15 15:01:32 +02:00
statuses , _ , err := git_model . GetLatestCommitStatus ( ctx , ctx . Repo . Repository . ID , latestCommit . ID . String ( ) , db . ListOptions { } )
if err != nil {
log . Error ( "GetLatestCommitStatus: %v" , err )
}
2021-10-08 15:08:22 +02:00
2022-07-15 15:01:32 +02:00
ctx . Data [ "LatestCommitStatus" ] = git_model . CalcCommitStatus ( statuses )
ctx . Data [ "LatestCommitStatuses" ] = statuses
}
2021-10-08 15:08:22 +02:00
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
treeLink := branchLink
if len ( ctx . Repo . TreePath ) > 0 {
2021-11-16 19:18:25 +01:00
treeLink += "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2021-10-08 15:08:22 +02:00
}
ctx . Data [ "TreeLink" ] = treeLink
ctx . Data [ "SSHDomain" ] = setting . SSH . Domain
return allEntries
}
2020-02-11 10:34:17 +01:00
func renderLanguageStats ( ctx * context . Context ) {
2021-12-10 02:27:50 +01:00
langs , err := repo_model . GetTopLanguageStats ( ctx . Repo . Repository , 5 )
2020-02-11 10:34:17 +01:00
if err != nil {
ctx . ServerError ( "Repo.GetTopLanguageStats" , err )
return
}
ctx . Data [ "LanguageStats" ] = langs
}
2020-05-05 23:51:49 +02:00
func renderRepoTopics ( ctx * context . Context ) {
2021-12-12 16:48:20 +01:00
topics , _ , err := repo_model . FindTopics ( & repo_model . FindTopicOptions {
2020-05-05 23:51:49 +02:00
RepoID : ctx . Repo . Repository . ID ,
} )
if err != nil {
ctx . ServerError ( "models.FindTopics" , err )
return
}
ctx . Data [ "Topics" ] = topics
}
2017-05-18 16:54:24 +02:00
func renderCode ( ctx * context . Context ) {
2017-03-18 11:59:07 +01:00
ctx . Data [ "PageIsViewCode" ] = true
2019-01-18 01:01:04 +01:00
if ctx . Repo . Repository . IsEmpty {
2022-07-27 18:46:34 +02:00
reallyEmpty := true
var err error
if ctx . Repo . GitRepo != nil {
reallyEmpty , err = ctx . Repo . GitRepo . IsEmpty ( )
if err != nil {
ctx . ServerError ( "GitRepo.IsEmpty" , err )
return
}
2021-12-24 16:36:26 +01:00
}
if reallyEmpty {
ctx . HTML ( http . StatusOK , tplRepoEMPTY )
return
}
// the repo is not really empty, so we should update the modal in database
// such problem may be caused by:
// 1) an error occurs during pushing/receiving. 2) the user replaces an empty git repo manually
// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
// it's possible for a repository to be non-empty by that flag but still 500
// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
ctx . Repo . Repository . IsEmpty = false
2022-05-20 16:08:52 +02:00
if err = repo_model . UpdateRepositoryCols ( ctx , ctx . Repo . Repository , "is_empty" ) ; err != nil {
2021-12-24 16:36:26 +01:00
ctx . ServerError ( "UpdateRepositoryCols" , err )
return
}
2022-06-06 10:01:49 +02:00
if err = repo_module . UpdateRepoSize ( ctx , ctx . Repo . Repository ) ; err != nil {
2021-12-24 16:36:26 +01:00
ctx . ServerError ( "UpdateRepoSize" , err )
return
}
2017-03-18 11:59:07 +01:00
}
2016-08-30 11:08:38 +02: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 03:04:25 +01:00
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
2016-08-30 11:08:38 +02:00
treeLink := branchLink
2017-10-30 03:04:25 +01:00
rawLink := ctx . Repo . RepoLink + "/raw/" + ctx . Repo . BranchNameSubURL ( )
2016-08-30 11:08:38 +02:00
if len ( ctx . Repo . TreePath ) > 0 {
2021-11-16 19:18:25 +01:00
treeLink += "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2016-08-30 11:08:38 +02:00
}
2018-04-11 04:51:44 +02:00
// Get Topics of this repo
2020-05-05 23:51:49 +02:00
renderRepoTopics ( ctx )
if ctx . Written ( ) {
2018-04-11 04:51:44 +02:00
return
}
2016-08-30 11:08:38 +02: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 10:34:17 +01:00
renderLanguageStats ( ctx )
if ctx . Written ( ) {
return
}
2016-08-30 11:08:38 +02:00
if entry . IsDir ( ) {
renderDirectory ( ctx , treeLink )
} else {
renderFile ( ctx , entry , treeLink , rawLink )
}
if ctx . Written ( ) {
return
}
2014-07-26 06:24:27 +02:00
2016-08-30 11:08:38 +02: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 06:24:27 +02:00
}
ctx . Data [ "HasParentPath" ] = true
2016-08-09 21:56:00 +02:00
if len ( paths ) - 2 >= 0 {
ctx . Data [ "ParentPath" ] = "/" + paths [ len ( paths ) - 2 ]
2014-07-26 06:24:27 +02:00
}
}
2016-08-09 21:56:00 +02:00
ctx . Data [ "Paths" ] = paths
2016-08-28 00:25:01 +02:00
ctx . Data [ "TreeLink" ] = treeLink
2016-08-30 11:08:38 +02:00
ctx . Data [ "TreeNames" ] = treeNames
2014-07-26 06:24:27 +02:00
ctx . Data [ "BranchLink" ] = branchLink
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplRepoHome )
2014-07-26 06:24:27 +02:00
}
2015-11-17 05:28:46 +01:00
2021-07-08 13:38:13 +02:00
// RenderUserCards render a page show users according the input template
2021-11-24 10:49:20 +01:00
func RenderUserCards ( ctx * context . Context , total int , getter func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) , tpl base . TplName ) {
2021-07-29 03:42:15 +02:00
page := ctx . FormInt ( "page" )
2015-11-17 05:28:46 +01:00
if page <= 0 {
page = 1
}
2022-06-12 17:51:54 +02:00
pager := context . NewPagination ( total , setting . ItemsPerPage , page , 5 )
2015-11-17 05:28:46 +01:00
ctx . Data [ "Page" ] = pager
2021-09-24 13:32:56 +02:00
items , err := getter ( db . ListOptions {
2021-02-04 18:23:46 +01:00
Page : pager . Paginater . Current ( ) ,
2022-06-12 17:51:54 +02:00
PageSize : setting . ItemsPerPage ,
2021-02-04 18:23:46 +01:00
} )
2015-11-17 05:28:46 +01:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx . ServerError ( "getter" , err )
2015-11-17 05:28:46 +01:00
return
}
2015-12-21 13:24:11 +01:00
ctx . Data [ "Cards" ] = items
2015-11-17 05:28:46 +01:00
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tpl )
2015-11-17 05:28:46 +01:00
}
2016-11-21 11:03:29 +01:00
// Watchers render repository's watch users
2016-03-11 17:56:52 +01:00
func Watchers ( ctx * context . Context ) {
2015-11-17 05:28:46 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.watchers" )
2015-12-21 13:24:11 +01:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.watchers" )
2015-11-17 05:28:46 +01:00
ctx . Data [ "PageIsWatchers" ] = true
2019-04-20 06:15:19 +02:00
2021-12-10 02:27:50 +01:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumWatches , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2021-12-12 16:48:20 +01:00
return repo_model . GetRepoWatchers ( ctx . Repo . Repository . ID , opts )
2021-12-10 02:27:50 +01:00
} , tplWatchers )
2015-11-17 05:28:46 +01:00
}
2016-11-21 11:03:29 +01:00
// Stars render repository's starred users
2016-03-11 17:56:52 +01:00
func Stars ( ctx * context . Context ) {
2015-11-17 05:28:46 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.stargazers" )
2015-12-21 13:24:11 +01:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.stargazers" )
2015-11-17 05:28:46 +01:00
ctx . Data [ "PageIsStargazers" ] = true
2021-11-24 10:49:20 +01:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumStars , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2021-12-12 16:48:20 +01:00
return repo_model . GetStargazers ( ctx . Repo . Repository , opts )
2021-11-22 16:21:55 +01:00
} , tplWatchers )
2015-11-17 05:28:46 +01:00
}
2015-11-17 05:33:40 +01:00
2016-11-21 11:03:29 +01:00
// Forks render repository's forked users
2016-03-11 17:56:52 +01:00
func Forks ( ctx * context . Context ) {
2015-11-17 05:33:40 +01:00
ctx . Data [ "Title" ] = ctx . Tr ( "repos.forks" )
2021-11-18 15:45:56 +01:00
page := ctx . FormInt ( "page" )
if page <= 0 {
page = 1
}
2022-06-12 17:51:54 +02:00
pager := context . NewPagination ( ctx . Repo . Repository . NumForks , setting . ItemsPerPage , page , 5 )
2021-11-18 15:45:56 +01:00
ctx . Data [ "Page" ] = pager
2021-12-12 16:48:20 +01:00
forks , err := repo_model . GetForks ( ctx . Repo . Repository , db . ListOptions {
2021-11-18 15:45:56 +01:00
Page : pager . Paginater . Current ( ) ,
2022-06-12 17:51:54 +02:00
PageSize : setting . ItemsPerPage ,
2021-11-18 15:45:56 +01:00
} )
2015-11-17 05:33:40 +01:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx . ServerError ( "GetForks" , err )
2015-11-17 05:33:40 +01:00
return
}
for _ , fork := range forks {
2022-03-22 16:22:54 +01:00
if err = fork . GetOwner ( ctx ) ; err != nil {
2018-01-10 22:34:17 +01:00
ctx . ServerError ( "GetOwner" , err )
2015-11-17 05:33:40 +01:00
return
}
}
2021-11-18 15:45:56 +01:00
2015-11-17 05:33:40 +01:00
ctx . Data [ "Forks" ] = forks
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplForks )
2015-11-17 05:33:40 +01:00
}