2017-09-14 12:21:32 +05:30
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-07-26 09:54:27 +05:30
// Copyright 2014 The Gogs Authors. All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2014-07-26 09:54:27 +05:30
package repo
import (
"bytes"
2021-10-08 18:38:22 +05:30
gocontext "context"
2017-01-30 01:43:57 +05:30
"encoding/base64"
2016-08-10 01:26:00 +05:30
"fmt"
2023-12-17 20:08:54 +05:30
"html/template"
2023-07-31 10:34:45 +05:30
"image"
2021-01-22 23:19:13 +05:30
"io"
2021-04-05 21:00:52 +05:30
"net/http"
2019-10-13 18:53:14 +05:30
"net/url"
2014-07-26 09:54:27 +05:30
"path"
2023-09-07 15:07:47 +05:30
"slices"
2014-07-26 09:54:27 +05:30
"strings"
2021-10-08 18:38:22 +05:30
"time"
2014-07-26 09:54:27 +05:30
2023-07-31 10:34:45 +05:30
_ "image/gif" // for processing gif images
_ "image/jpeg" // for processing jpeg images
_ "image/png" // for processing png images
2022-08-25 08:01:57 +05:30
activities_model "code.gitea.io/gitea/models/activities"
admin_model "code.gitea.io/gitea/models/admin"
2021-12-10 13:44:24 +05:30
asymkey_model "code.gitea.io/gitea/models/asymkey"
2021-09-24 17:02:56 +05:30
"code.gitea.io/gitea/models/db"
2022-06-12 21:21:54 +05:30
git_model "code.gitea.io/gitea/models/git"
2023-06-08 14:26:05 +05:30
issue_model "code.gitea.io/gitea/models/issues"
2021-12-10 06:57:50 +05:30
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 01:27:58 +05:30
unit_model "code.gitea.io/gitea/models/unit"
2021-11-24 15:19:20 +05:30
user_model "code.gitea.io/gitea/models/user"
2023-04-23 07:46:54 +05:30
"code.gitea.io/gitea/modules/actions"
2016-11-10 21:54:48 +05:30
"code.gitea.io/gitea/modules/base"
2019-08-15 17:37:28 +05:30
"code.gitea.io/gitea/modules/charset"
2022-10-12 10:48:26 +05:30
"code.gitea.io/gitea/modules/container"
2016-11-10 21:54:48 +05:30
"code.gitea.io/gitea/modules/context"
2019-03-27 15:03:00 +05:30
"code.gitea.io/gitea/modules/git"
2016-12-06 23:28:31 +05:30
"code.gitea.io/gitea/modules/highlight"
2016-12-26 06:46:37 +05:30
"code.gitea.io/gitea/modules/lfs"
2016-11-10 21:54:48 +05:30
"code.gitea.io/gitea/modules/log"
2017-04-21 12:31:08 +05:30
"code.gitea.io/gitea/modules/markup"
2022-06-06 13:31:49 +05:30
repo_module "code.gitea.io/gitea/modules/repository"
2016-11-10 21:54:48 +05:30
"code.gitea.io/gitea/modules/setting"
2021-07-01 00:44:53 +05:30
"code.gitea.io/gitea/modules/structs"
2021-06-05 18:02:19 +05:30
"code.gitea.io/gitea/modules/typesniffer"
2021-10-25 02:42:43 +05:30
"code.gitea.io/gitea/modules/util"
2022-03-13 22:10:47 +05:30
"code.gitea.io/gitea/routers/web/feed"
2023-05-09 05:00:14 +05:30
issue_service "code.gitea.io/gitea/services/issue"
2023-04-23 07:46:54 +05:30
"github.com/nektos/act/pkg/model"
2023-07-31 10:34:45 +05:30
_ "golang.org/x/image/bmp" // for processing bmp images
_ "golang.org/x/image/webp" // for processing webp images
2014-07-26 09:54:27 +05:30
)
const (
2021-10-08 18:38:22 +05:30
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 09:54:27 +05:30
)
2023-02-12 12:38:10 +05:30
// locate a README for a tree in one of the supported paths.
//
// entries is passed to reduce calls to ListEntries(), so
// this has precondition:
//
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
//
2020-02-22 04:34:20 +05:30
// FIXME: There has to be a more efficient way of doing this
2023-04-11 08:30:19 +05:30
func findReadmeFileInEntries ( ctx * context . Context , entries [ ] * git . TreeEntry , tryWellKnownDirs bool ) ( string , * git . TreeEntry , error ) {
2022-08-01 04:06:58 +05:30
// 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
2023-05-08 15:06:54 +05:30
exts := append ( localizedExtensions ( ".md" , ctx . Locale . Language ( ) ) , ".txt" , "" ) // sorted by priority
2022-08-01 04:06:58 +05:30
extCount := len ( exts )
2023-03-16 03:21:39 +05:30
readmeFiles := make ( [ ] * git . TreeEntry , extCount + 1 )
2023-02-12 12:38:10 +05:30
docsEntries := make ( [ ] * git . TreeEntry , 3 ) // (one of docs/, .gitea/ or .github/)
2020-02-22 04:34:20 +05:30
for _ , entry := range entries {
2023-04-11 08:30:19 +05:30
if tryWellKnownDirs && entry . IsDir ( ) {
2023-02-12 12:38:10 +05:30
// as a special case for the top-level repo introduction README,
// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
// (note that docsEntries is ignored unless we are at the root)
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
}
}
2020-02-22 04:34:20 +05:30
continue
}
2023-02-14 01:31:09 +05:30
if i , ok := util . IsReadmeFileExtension ( entry . Name ( ) , exts ... ) ; ok {
2023-02-12 12:38:10 +05:30
log . Debug ( "Potential readme file: %s" , entry . Name ( ) )
2023-03-16 03:21:39 +05:30
if readmeFiles [ i ] == nil || base . NaturalSortLess ( readmeFiles [ i ] . Name ( ) , entry . Blob ( ) . Name ( ) ) {
if entry . IsLink ( ) {
target , err := entry . FollowLinks ( )
2020-02-22 04:34:20 +05:30
if err != nil && ! git . IsErrBadLink ( err ) {
2023-03-16 03:21:39 +05:30
return "" , nil , err
} else if target != nil && ( target . IsExecutable ( ) || target . IsRegular ( ) ) {
readmeFiles [ i ] = entry
2020-02-22 04:34:20 +05:30
}
2023-03-16 03:21:39 +05:30
} else {
readmeFiles [ i ] = entry
2020-02-22 04:34:20 +05:30
}
}
}
}
2023-03-16 03:21:39 +05:30
var readmeFile * git . TreeEntry
2020-02-22 04:34:20 +05:30
for _ , f := range readmeFiles {
if f != nil {
readmeFile = f
break
}
}
2023-02-12 12:38:10 +05:30
if ctx . Repo . TreePath == "" && readmeFile == nil {
for _ , subTreeEntry := range docsEntries {
if subTreeEntry == nil {
continue
}
subTree := subTreeEntry . Tree ( )
if subTree == nil {
// this should be impossible; if subTreeEntry exists so should this.
continue
}
var err error
childEntries , err := subTree . ListEntries ( )
if err != nil {
2023-03-16 03:21:39 +05:30
return "" , nil , err
2023-02-12 12:38:10 +05:30
}
2023-03-16 03:21:39 +05:30
2023-04-11 08:30:19 +05:30
subfolder , readmeFile , err := findReadmeFileInEntries ( ctx , childEntries , false )
2023-02-12 12:38:10 +05:30
if err != nil && ! git . IsErrNotExist ( err ) {
2023-03-16 03:21:39 +05:30
return "" , nil , err
2023-02-12 12:38:10 +05:30
}
if readmeFile != nil {
2023-03-16 03:21:39 +05:30
return path . Join ( subTreeEntry . Name ( ) , subfolder ) , readmeFile , nil
2023-02-12 12:38:10 +05:30
}
}
}
2023-03-16 03:21:39 +05:30
return "" , readmeFile , nil
2020-02-22 04:34:20 +05:30
}
2024-01-15 14:19:24 +05:30
func renderDirectory ( ctx * context . Context ) {
2021-10-08 18:38:22 +05:30
entries := renderDirectoryFiles ( ctx , 1 * time . Second )
if ctx . Written ( ) {
2014-07-26 09:54:27 +05:30
return
}
2021-12-15 13:20:11 +05:30
if ctx . Repo . TreePath != "" {
2022-10-13 14:01:10 +05:30
ctx . Data [ "HideRepoInfo" ] = true
2021-12-15 13:20:11 +05:30
ctx . Data [ "Title" ] = ctx . Tr ( "repo.file.title" , ctx . Repo . Repository . Name + "/" + path . Base ( ctx . Repo . TreePath ) , ctx . Repo . RefName )
}
2023-04-11 08:30:19 +05:30
subfolder , readmeFile , err := findReadmeFileInEntries ( ctx , entries , true )
2023-02-12 12:38:10 +05:30
if err != nil {
ctx . ServerError ( "findReadmeFileInEntries" , err )
return
}
2022-04-27 02:01:15 +05:30
2024-01-15 14:19:24 +05:30
renderReadmeFile ( ctx , subfolder , readmeFile )
2022-04-27 02:01:15 +05:30
}
2022-08-01 04:06:58 +05:30
// 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-08 00:10:36 +05:30
// regional identifier to the provided extension.
2022-08-01 04:06:58 +05:30
// 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 , "-" )
2022-10-24 11:42:15 +05:30
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
return [ ] string { lowerLangCode + ext , underscoreLangCode + ext , lowerLangCode [ : indexOfDash ] + ext , "_" + lowerLangCode [ 1 : indexOfDash ] + ext , ext }
2022-08-01 04:06:58 +05:30
}
// e.g. [.en.md, .md]
return [ ] string { lowerLangCode + ext , ext }
}
2022-12-14 15:41:11 +05:30
type fileInfo struct {
isTextFile bool
isLFSFile bool
fileSize int64
lfsMeta * lfs . Pointer
st typesniffer . SniffedType
}
2016-08-30 14:38:38 +05:30
2023-07-24 09:17:27 +05:30
func getFileReader ( ctx gocontext . Context , repoID int64 , blob * git . Blob ) ( [ ] byte , io . ReadCloser , * fileInfo , error ) {
2022-12-14 15:41:11 +05:30
dataRc , err := blob . DataAsync ( )
2022-04-27 02:01:15 +05:30
if err != nil {
2022-12-14 15:41:11 +05:30
return nil , nil , nil , err
2022-04-27 02:01:15 +05:30
}
buf := make ( [ ] byte , 1024 )
n , _ := util . ReadAtMost ( dataRc , buf )
buf = buf [ : n ]
st := typesniffer . DetectContentType ( buf )
isTextFile := st . IsText ( )
// FIXME: what happens when README file is an image?
2022-12-14 15:41:11 +05:30
if ! isTextFile || ! setting . LFS . StartServer {
return buf , dataRc , & fileInfo { isTextFile , false , blob . Size ( ) , nil , st } , nil
}
2022-04-27 02:01:15 +05:30
2022-12-14 15:41:11 +05:30
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
if ! pointer . IsValid ( ) { // fallback to plain file
return buf , dataRc , & fileInfo { isTextFile , false , blob . Size ( ) , nil , st } , nil
}
2019-02-22 02:27:16 +05:30
2023-07-24 09:17:27 +05:30
meta , err := git_model . GetLFSMetaObjectByOid ( ctx , repoID , pointer . Oid )
2022-12-21 06:51:26 +05:30
if err != nil && err != git_model . ErrLFSObjectNotExist { // fallback to plain file
2022-12-14 15:41:11 +05:30
return buf , dataRc , & fileInfo { isTextFile , false , blob . Size ( ) , nil , st } , nil
}
2019-02-22 02:27:16 +05:30
2022-12-14 15:41:11 +05:30
dataRc . Close ( )
if err != nil {
return nil , nil , nil , err
}
2019-02-22 02:27:16 +05:30
2022-12-14 15:41:11 +05:30
dataRc , err = lfs . ReadMetaObject ( pointer )
if err != nil {
return nil , nil , nil , err
2022-04-27 02:01:15 +05:30
}
2019-02-22 02:27:16 +05:30
2022-12-14 15:41:11 +05:30
buf = make ( [ ] byte , 1024 )
n , err = util . ReadAtMost ( dataRc , buf )
if err != nil {
dataRc . Close ( )
return nil , nil , nil , err
}
buf = buf [ : n ]
st = typesniffer . DetectContentType ( buf )
return buf , dataRc , & fileInfo { st . IsText ( ) , true , meta . Size , & meta . Pointer , st } , nil
}
2024-01-15 14:19:24 +05:30
func renderReadmeFile ( ctx * context . Context , subfolder string , readmeFile * git . TreeEntry ) {
2023-03-16 03:21:39 +05:30
target := readmeFile
if readmeFile != nil && readmeFile . IsLink ( ) {
target , _ = readmeFile . FollowLinks ( )
}
if target == nil {
// if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
// simply skip rendering the README
return
}
2022-12-14 15:41:11 +05:30
ctx . Data [ "RawFileLink" ] = ""
ctx . Data [ "ReadmeInList" ] = true
ctx . Data [ "ReadmeExist" ] = true
2023-03-16 03:21:39 +05:30
ctx . Data [ "FileIsSymlink" ] = readmeFile . IsLink ( )
2022-12-14 15:41:11 +05:30
2023-07-24 09:17:27 +05:30
buf , dataRc , fInfo , err := getFileReader ( ctx , ctx . Repo . Repository . ID , target . Blob ( ) )
2022-12-14 15:41:11 +05:30
if err != nil {
ctx . ServerError ( "getFileReader" , err )
2022-04-27 02:01:15 +05:30
return
}
2022-12-14 15:41:11 +05:30
defer dataRc . Close ( )
ctx . Data [ "FileIsText" ] = fInfo . isTextFile
2023-03-16 03:21:39 +05:30
ctx . Data [ "FileName" ] = path . Join ( subfolder , readmeFile . Name ( ) )
2022-12-14 15:41:11 +05:30
ctx . Data [ "IsLFSFile" ] = fInfo . isLFSFile
2019-02-22 02:27:16 +05:30
2022-12-14 15:41:11 +05:30
if fInfo . isLFSFile {
2023-03-16 03:21:39 +05:30
filenameBase64 := base64 . RawURLEncoding . EncodeToString ( [ ] byte ( readmeFile . Name ( ) ) )
2023-02-11 12:04:11 +05:30
ctx . Data [ "RawFileLink" ] = fmt . Sprintf ( "%s.git/info/lfs/objects/%s/%s" , ctx . Repo . Repository . Link ( ) , url . PathEscape ( fInfo . lfsMeta . Oid ) , url . PathEscape ( filenameBase64 ) )
2022-04-27 02:01:15 +05:30
}
2022-01-07 06:48:52 +05:30
2022-12-14 15:41:11 +05:30
if ! fInfo . isTextFile {
return
}
if fInfo . fileSize >= setting . UI . MaxDisplayFileSize {
2022-04-27 02:01:15 +05:30
// 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
2022-12-14 15:41:11 +05:30
ctx . Data [ "FileSize" ] = fInfo . fileSize
2022-04-27 02:01:15 +05:30
return
2016-08-30 14:38:38 +05:30
}
2016-08-15 11:32:14 +05:30
2024-01-27 23:32:51 +05:30
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) , charset . ConvertOpts { } )
2022-04-27 02:01:15 +05:30
2023-03-16 03:21:39 +05:30
if markupType := markup . Type ( readmeFile . Name ( ) ) ; markupType != "" {
2022-04-27 02:01:15 +05:30
ctx . Data [ "IsMarkup" ] = true
2022-06-20 15:32:49 +05:30
ctx . Data [ "MarkupType" ] = markupType
2022-08-14 00:02:34 +05:30
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 09:03:23 +05:30
Ctx : ctx ,
2023-03-16 03:21:39 +05:30
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).
2024-01-15 14:19:24 +05:30
Links : markup . Links {
Base : ctx . Repo . RepoLink ,
BranchPath : ctx . Repo . BranchNameSubURL ( ) ,
TreePath : path . Join ( ctx . Repo . TreePath , subfolder ) ,
} ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ctx ) ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-14 00:02:34 +05:30
} , rd )
2022-04-27 02:01:15 +05:30
if err != nil {
2023-03-16 03:21:39 +05:30
log . Error ( "Render failed for %s in %-v: %v Falling back to rendering source" , readmeFile . Name ( ) , ctx . Repo . Repository , err )
2023-12-17 20:08:54 +05:30
delete ( ctx . Data , "IsMarkup" )
2022-04-27 02:01:15 +05:30
}
2023-12-17 20:08:54 +05:30
}
if ctx . Data [ "IsMarkup" ] != true {
2022-12-18 01:52:25 +05:30
ctx . Data [ "IsPlainText" ] = true
2023-12-17 20:08:54 +05:30
content , err := io . ReadAll ( rd )
2022-04-27 02:01:15 +05:30
if err != nil {
2023-12-17 20:08:54 +05:30
log . Error ( "Read readme content failed: %v" , err )
2022-04-27 02:01:15 +05:30
}
2023-12-17 20:08:54 +05:30
contentEscaped := template . HTMLEscapeString ( util . UnsafeBytesToString ( content ) )
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] = charset . EscapeControlHTML ( template . HTML ( contentEscaped ) , ctx . Locale )
2016-08-30 14:38:38 +05:30
}
2023-11-20 17:17:55 +05:30
if ! fInfo . isLFSFile && ctx . Repo . CanEnableEditor ( ctx , ctx . Doer ) {
ctx . Data [ "CanEditReadmeFile" ] = true
}
2016-08-30 14:38:38 +05:30
}
2014-09-30 14:09:53 +05:30
2024-01-15 22:12:15 +05:30
func loadLatestCommitData ( ctx * context . Context , latestCommit * git . Commit ) bool {
// Show latest commit info of repository in table header,
// or of directory if not in root directory.
ctx . Data [ "LatestCommit" ] = latestCommit
if latestCommit != nil {
verification := asymkey_model . ParseCommitWithSignature ( ctx , latestCommit )
if err := asymkey_model . CalculateTrustStatus ( verification , ctx . Repo . Repository . GetTrustModel ( ) , func ( user * user_model . User ) ( bool , error ) {
return repo_model . IsOwnerMemberCollaborator ( ctx , ctx . Repo . Repository , user . ID )
} , nil ) ; err != nil {
ctx . ServerError ( "CalculateTrustStatus" , err )
return false
}
ctx . Data [ "LatestCommitVerification" ] = verification
ctx . Data [ "LatestCommitUser" ] = user_model . ValidateCommitWithEmail ( ctx , latestCommit )
statuses , _ , err := git_model . GetLatestCommitStatus ( ctx , ctx . Repo . Repository . ID , latestCommit . ID . String ( ) , db . ListOptions { ListAll : true } )
if err != nil {
log . Error ( "GetLatestCommitStatus: %v" , err )
}
ctx . Data [ "LatestCommitStatus" ] = git_model . CalcCommitStatus ( statuses )
ctx . Data [ "LatestCommitStatuses" ] = statuses
}
return true
}
2024-01-15 14:19:24 +05:30
func renderFile ( ctx * context . Context , entry * git . TreeEntry ) {
2016-08-30 14:38:38 +05:30
ctx . Data [ "IsViewFile" ] = true
2022-10-13 14:01:10 +05:30
ctx . Data [ "HideRepoInfo" ] = true
2016-08-30 14:38:38 +05:30
blob := entry . Blob ( )
2023-07-24 09:17:27 +05:30
buf , dataRc , fInfo , err := getFileReader ( ctx , ctx . Repo . Repository . ID , blob )
2016-08-30 14:38:38 +05:30
if err != nil {
2022-12-14 15:41:11 +05:30
ctx . ServerError ( "getFileReader" , err )
2016-08-30 14:38:38 +05:30
return
}
2017-11-29 07:20:39 +05:30
defer dataRc . Close ( )
2014-07-26 09:54:27 +05:30
2021-12-15 13:20:11 +05:30
ctx . Data [ "Title" ] = ctx . Tr ( "repo.file.title" , ctx . Repo . Repository . Name + "/" + path . Base ( ctx . Repo . TreePath ) , ctx . Repo . RefName )
2020-02-22 04:34:20 +05:30
ctx . Data [ "FileIsSymlink" ] = entry . IsLink ( )
2016-08-30 14:38:38 +05:30
ctx . Data [ "FileName" ] = blob . Name ( )
2024-01-15 14:19:24 +05:30
ctx . Data [ "RawFileLink" ] = ctx . Repo . RepoLink + "/raw/" + ctx . Repo . BranchNameSubURL ( ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2016-08-30 14:38:38 +05:30
2024-01-15 22:12:15 +05:30
commit , err := ctx . Repo . Commit . GetCommitByPath ( ctx . Repo . TreePath )
if err != nil {
ctx . ServerError ( "GetCommitByPath" , err )
return
}
if ! loadLatestCommitData ( ctx , commit ) {
return
}
2022-09-12 03:46:56 +05:30
if ctx . Repo . TreePath == ".editorconfig" {
2023-04-07 01:31:20 +05:30
_ , editorconfigWarning , editorconfigErr := ctx . Repo . GetEditorconfig ( ctx . Repo . Commit )
if editorconfigWarning != nil {
ctx . Data [ "FileWarning" ] = strings . TrimSpace ( editorconfigWarning . Error ( ) )
}
if editorconfigErr != nil {
ctx . Data [ "FileError" ] = strings . TrimSpace ( editorconfigErr . Error ( ) )
}
2023-05-09 05:00:14 +05:30
} else if issue_service . IsTemplateConfig ( ctx . Repo . TreePath ) {
_ , issueConfigErr := issue_service . GetTemplateConfig ( ctx . Repo . GitRepo , ctx . Repo . TreePath , ctx . Repo . Commit )
2023-04-07 01:31:20 +05:30
if issueConfigErr != nil {
ctx . Data [ "FileError" ] = strings . TrimSpace ( issueConfigErr . Error ( ) )
}
2023-04-23 07:46:54 +05:30
} else if actions . IsWorkflow ( ctx . Repo . TreePath ) {
content , err := actions . GetContentFromEntry ( entry )
if err != nil {
log . Error ( "actions.GetContentFromEntry: %v" , err )
}
_ , workFlowErr := model . ReadWorkflow ( bytes . NewReader ( content ) )
if workFlowErr != nil {
ctx . Data [ "FileError" ] = ctx . Locale . Tr ( "actions.runs.invalid_workflow_helper" , workFlowErr . Error ( ) )
}
2023-09-07 15:07:47 +05:30
} else if slices . Contains ( [ ] string { "CODEOWNERS" , "docs/CODEOWNERS" , ".gitea/CODEOWNERS" } , ctx . Repo . TreePath ) {
2023-06-13 14:32:25 +05:30
if data , err := blob . GetBlobContent ( setting . UI . MaxDisplayFileSize ) ; err == nil {
2023-06-08 14:26:05 +05:30
_ , warnings := issue_model . GetCodeOwnersFromContent ( ctx , data )
if len ( warnings ) > 0 {
ctx . Data [ "FileWarning" ] = strings . Join ( warnings , "\n" )
}
}
2022-09-12 03:46:56 +05:30
}
2021-08-11 06:01:13 +05:30
isDisplayingSource := ctx . FormString ( "display" ) == "source"
2021-01-13 09:15:19 +05:30
isDisplayingRendered := ! isDisplayingSource
2022-12-14 15:41:11 +05:30
if fInfo . isLFSFile {
ctx . Data [ "RawFileLink" ] = ctx . Repo . RepoLink + "/media/" + ctx . Repo . BranchNameSubURL ( ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2016-12-26 06:46:37 +05:30
}
2021-02-05 06:59:42 +05:30
2022-12-14 15:41:11 +05:30
isRepresentableAsText := fInfo . st . IsRepresentableAsText ( )
2021-02-05 06:59:42 +05:30
if ! isRepresentableAsText {
// If we can't show plain text, always try to render.
isDisplayingSource = false
isDisplayingRendered = true
}
2022-12-14 15:41:11 +05:30
ctx . Data [ "IsLFSFile" ] = fInfo . isLFSFile
ctx . Data [ "FileSize" ] = fInfo . fileSize
ctx . Data [ "IsTextFile" ] = fInfo . isTextFile
2021-02-05 06:59:42 +05:30
ctx . Data [ "IsRepresentableAsText" ] = isRepresentableAsText
ctx . Data [ "IsDisplayingSource" ] = isDisplayingSource
ctx . Data [ "IsDisplayingRendered" ] = isDisplayingRendered
2023-06-16 11:16:12 +05:30
ctx . Data [ "IsExecutable" ] = entry . IsExecutable ( )
2022-11-21 15:29:42 +05:30
2022-12-14 15:41:11 +05:30
isTextSource := fInfo . isTextFile || isDisplayingSource
2022-11-21 15:29:42 +05:30
ctx . Data [ "IsTextSource" ] = isTextSource
if isTextSource {
ctx . Data [ "CanCopyContent" ] = true
}
2021-02-05 06:59:42 +05:30
2019-10-30 03:02:21 +05:30
// Check LFS Lock
2023-01-09 09:20:54 +05:30
lfsLock , err := git_model . GetTreePathLock ( ctx , ctx . Repo . Repository . ID , ctx . Repo . TreePath )
2019-10-30 03:02:21 +05:30
ctx . Data [ "LFSLock" ] = lfsLock
if err != nil {
ctx . ServerError ( "GetTreePathLock" , err )
return
}
if lfsLock != nil {
2022-12-03 08:18:26 +05:30
u , err := user_model . GetUserByID ( ctx , lfsLock . OwnerID )
2021-11-24 15:19:20 +05:30
if err != nil {
ctx . ServerError ( "GetTreePathLock" , err )
return
}
2022-10-12 06:33:15 +05:30
ctx . Data [ "LFSLockOwner" ] = u . Name
2022-02-16 21:52:25 +05:30
ctx . Data [ "LFSLockOwnerHomeLink" ] = u . HomeLink ( )
2019-10-30 03:02:21 +05:30
ctx . Data [ "LFSLockHint" ] = ctx . Tr ( "repo.editor.this_file_locked" )
}
2016-12-26 06:46:37 +05:30
2016-08-30 14:38:38 +05:30
// Assume file is not editable first.
2022-12-14 15:41:11 +05:30
if fInfo . isLFSFile {
2019-02-12 20:39:43 +05:30
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_lfs_files" )
2021-01-13 09:15:19 +05:30
} else if ! isRepresentableAsText {
2016-08-30 14:38:38 +05:30
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.cannot_edit_non_text_files" )
}
switch {
2021-01-13 09:15:19 +05:30
case isRepresentableAsText :
2022-12-14 15:41:11 +05:30
if fInfo . st . IsSvgImage ( ) {
2021-01-13 09:15:19 +05:30
ctx . Data [ "IsImageFile" ] = true
2022-11-21 15:29:42 +05:30
ctx . Data [ "CanCopyContent" ] = true
2021-01-13 09:15:19 +05:30
ctx . Data [ "HasSourceRenderedToggle" ] = true
}
2022-12-14 15:41:11 +05:30
if fInfo . fileSize >= setting . UI . MaxDisplayFileSize {
2016-08-30 14:38:38 +05:30
ctx . Data [ "IsFileTooLarge" ] = true
break
2014-07-26 09:54:27 +05:30
}
2024-01-27 23:32:51 +05:30
rd := charset . ToUTF8WithFallbackReader ( io . MultiReader ( bytes . NewReader ( buf ) , dataRc ) , charset . ConvertOpts { } )
2022-04-10 20:31:35 +05:30
shouldRenderSource := ctx . FormString ( "display" ) == "source"
2023-02-14 01:31:09 +05:30
readmeExist := util . IsReadmeFileName ( blob . Name ( ) )
2016-08-30 14:38:38 +05:30
ctx . Data [ "ReadmeExist" ] = readmeExist
2022-04-10 20:31:35 +05:30
markupType := markup . Type ( blob . Name ( ) )
2022-06-09 03:16:39 +05:30
// 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 20:31:35 +05:30
if markupType != "" {
ctx . Data [ "HasSourceRenderedToggle" ] = true
}
if markupType != "" && ! shouldRenderSource {
2017-10-17 04:47:22 +05:30
ctx . Data [ "IsMarkup" ] = true
2019-08-16 03:39:50 +05:30
ctx . Data [ "MarkupType" ] = markupType
2022-06-09 03:16:39 +05:30
if ! detected {
markupType = ""
}
2023-10-11 09:54:07 +05:30
metas := ctx . Repo . Repository . ComposeDocumentMetas ( ctx )
2022-06-16 09:03:23 +05:30
metas [ "BranchNameSubURL" ] = ctx . Repo . BranchNameSubURL ( )
2022-08-14 00:02:34 +05:30
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 09:03:23 +05:30
Ctx : ctx ,
Type : markupType ,
RelativePath : ctx . Repo . TreePath ,
2024-01-15 14:19:24 +05:30
Links : markup . Links {
Base : ctx . Repo . RepoLink ,
BranchPath : ctx . Repo . BranchNameSubURL ( ) ,
TreePath : path . Dir ( ctx . Repo . TreePath ) ,
} ,
Metas : metas ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-14 00:02:34 +05:30
} , rd )
2021-04-20 03:55:08 +05:30
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
2022-06-16 09:03:23 +05:30
// to prevent iframe load third-party url
ctx . Resp . Header ( ) . Add ( "Content-Security-Policy" , "frame-src 'self'" )
2016-08-30 14:38:38 +05:30
} else {
2021-09-22 11:08:34 +05:30
buf , _ := io . ReadAll ( rd )
2022-07-31 00:47:43 +05:30
2023-10-02 12:53:18 +05:30
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
2023-12-17 20:08:54 +05:30
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
2023-10-02 12:53:18 +05:30
// Gitea uses the definition (like most modern editors):
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
// To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
// This NumLines is only used for the display on the UI: "xxx lines"
2022-07-31 00:47:43 +05:30
if len ( buf ) == 0 {
ctx . Data [ "NumLines" ] = 0
} else {
ctx . Data [ "NumLines" ] = bytes . Count ( buf , [ ] byte { '\n' } ) + 1
}
2019-10-29 21:35:26 +05:30
ctx . Data [ "NumLinesSet" ] = true
2021-11-18 02:07:00 +05:30
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 ,
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 08:00:43 +05:30
Attributes : [ ] string { "linguist-language" , "gitlab-language" } ,
2021-11-18 02:07:00 +05:30
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-11-19 16:38:06 +05:30
fileContent , lexerName , err := highlight . File ( blob . Name ( ) , language , buf )
ctx . Data [ "LexerName" ] = lexerName
2022-07-31 00:47:43 +05:30
if err != nil {
log . Error ( "highlight.File failed, fallback to plain text: %v" , err )
fileContent = highlight . PlainText ( buf )
}
2022-08-14 00:02:34 +05:30
status := & charset . EscapeStatus { }
statuses := make ( [ ] * charset . EscapeStatus , len ( fileContent ) )
2022-01-07 06:48:52 +05:30
for i , line := range fileContent {
2022-08-14 00:02:34 +05:30
statuses [ i ] , fileContent [ i ] = charset . EscapeControlHTML ( line , ctx . Locale )
status = status . Or ( statuses [ i ] )
2022-01-07 06:48:52 +05:30
}
2022-08-14 00:02:34 +05:30
ctx . Data [ "EscapeStatus" ] = status
2022-01-07 06:48:52 +05:30
ctx . Data [ "FileContent" ] = fileContent
ctx . Data [ "LineEscapeStatus" ] = statuses
2014-09-26 18:25:13 +05:30
}
2022-12-14 15:41:11 +05:30
if ! fInfo . isLFSFile {
2023-07-22 19:44:27 +05:30
if ctx . Repo . CanEnableEditor ( ctx , ctx . Doer ) {
2022-03-22 12:33:22 +05:30
if lfsLock != nil && lfsLock . OwnerID != ctx . Doer . ID {
2019-10-30 03:02:21 +05:30
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 20:39:43 +05:30
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
2023-07-22 19:44:27 +05:30
} else if ! ctx . Repo . CanWriteToBranch ( ctx , ctx . Doer , ctx . Repo . BranchName ) {
2019-02-12 20:39:43 +05:30
ctx . Data [ "EditFileTooltip" ] = ctx . Tr ( "repo.editor.fork_before_edit" )
}
2016-08-28 14:11:44 +05:30
}
2016-08-30 14:38:38 +05:30
2022-12-14 15:41:11 +05:30
case fInfo . st . IsPDF ( ) :
2016-08-30 14:38:38 +05:30
ctx . Data [ "IsPDFFile" ] = true
2022-12-14 15:41:11 +05:30
case fInfo . st . IsVideo ( ) :
2016-12-20 13:39:11 +05:30
ctx . Data [ "IsVideoFile" ] = true
2022-12-14 15:41:11 +05:30
case fInfo . st . IsAudio ( ) :
2018-10-30 07:47:26 +05:30
ctx . Data [ "IsAudioFile" ] = true
2022-12-14 15:41:11 +05:30
case fInfo . st . IsImage ( ) && ( setting . UI . SVG . Enabled || ! fInfo . st . IsSvgImage ( ) ) :
2016-08-30 14:38:38 +05:30
ctx . Data [ "IsImageFile" ] = true
2022-11-21 15:29:42 +05:30
ctx . Data [ "CanCopyContent" ] = true
2019-10-21 12:24:18 +05:30
default :
2022-12-14 15:41:11 +05:30
if fInfo . fileSize >= setting . UI . MaxDisplayFileSize {
2019-10-21 12:24:18 +05:30
ctx . Data [ "IsFileTooLarge" ] = true
break
}
if markupType := markup . Type ( blob . Name ( ) ) ; markupType != "" {
2021-04-20 03:55:08 +05:30
rd := io . MultiReader ( bytes . NewReader ( buf ) , dataRc )
2019-10-21 12:24:18 +05:30
ctx . Data [ "IsMarkup" ] = true
ctx . Data [ "MarkupType" ] = markupType
2022-08-14 00:02:34 +05:30
ctx . Data [ "EscapeStatus" ] , ctx . Data [ "FileContent" ] , err = markupRender ( ctx , & markup . RenderContext {
2022-06-16 09:03:23 +05:30
Ctx : ctx ,
RelativePath : ctx . Repo . TreePath ,
2024-01-15 14:19:24 +05:30
Links : markup . Links {
Base : ctx . Repo . RepoLink ,
BranchPath : ctx . Repo . BranchNameSubURL ( ) ,
TreePath : path . Dir ( ctx . Repo . TreePath ) ,
} ,
Metas : ctx . Repo . Repository . ComposeDocumentMetas ( ctx ) ,
GitRepo : ctx . Repo . GitRepo ,
2022-08-14 00:02:34 +05:30
} , rd )
2021-04-20 03:55:08 +05:30
if err != nil {
ctx . ServerError ( "Render" , err )
return
}
2019-10-21 12:24:18 +05:30
}
2014-07-26 09:54:27 +05:30
}
2024-01-24 11:21:37 +05:30
if ctx . Repo . GitRepo != nil {
checker , deferable := ctx . Repo . GitRepo . CheckAttributeReader ( ctx . Repo . CommitID )
if checker != nil {
defer deferable ( )
attrs , err := checker . CheckPath ( ctx . Repo . TreePath )
if err == nil {
vendored , has := attrs [ "linguist-vendored" ]
ctx . Data [ "IsVendored" ] = has && ( vendored == "set" || vendored == "true" )
generated , has := attrs [ "linguist-generated" ]
ctx . Data [ "IsGenerated" ] = has && ( generated == "set" || generated == "true" )
}
}
}
2023-07-31 10:34:45 +05:30
if fInfo . st . IsImage ( ) && ! fInfo . st . IsSvgImage ( ) {
img , _ , err := image . DecodeConfig ( bytes . NewReader ( buf ) )
if err == nil {
// There are Image formats go can't decode
// Instead of throwing an error in that case, we show the size only when we can decode
ctx . Data [ "ImageSize" ] = fmt . Sprintf ( "%dx%dpx" , img . Width , img . Height )
}
}
2023-07-22 19:44:27 +05:30
if ctx . Repo . CanEnableEditor ( ctx , ctx . Doer ) {
2022-03-22 12:33:22 +05:30
if lfsLock != nil && lfsLock . OwnerID != ctx . Doer . ID {
2019-10-30 03:02:21 +05:30
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 14:38:38 +05:30
} else if ! ctx . Repo . IsViewBranch {
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_be_on_a_branch" )
2023-07-22 19:44:27 +05:30
} else if ! ctx . Repo . CanWriteToBranch ( ctx , ctx . Doer , ctx . Repo . BranchName ) {
2016-08-30 14:38:38 +05:30
ctx . Data [ "DeleteFileTooltip" ] = ctx . Tr ( "repo.editor.must_have_write_access" )
}
}
2023-12-17 20:08:54 +05:30
func markupRender ( ctx * context . Context , renderCtx * markup . RenderContext , input io . Reader ) ( escaped * charset . EscapeStatus , output template . HTML , err error ) {
2022-08-14 00:02:34 +05:30
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 )
2023-12-17 20:08:54 +05:30
output = template . HTML ( sb . String ( ) )
2022-08-14 00:02:34 +05:30
close ( done )
} ( )
err = markup . Render ( renderCtx , input , markupWr )
_ = markupWr . CloseWithError ( err )
<- done
return escaped , output , err
}
2021-10-08 18:38:22 +05:30
func checkHomeCodeViewable ( ctx * context . Context ) {
2018-11-28 16:56:14 +05:30
if len ( ctx . Repo . Units ) > 0 {
2019-10-13 18:53:14 +05:30
if ctx . Repo . Repository . IsBeingCreated ( ) {
2023-09-16 20:09:12 +05:30
task , err := admin_model . GetMigratingTask ( ctx , ctx . Repo . Repository . ID )
2019-10-13 18:53:14 +05:30
if err != nil {
2022-08-25 08:01:57 +05:30
if admin_model . IsErrTaskDoesNotExist ( err ) {
2021-11-13 16:58:50 +05:30
ctx . Data [ "Repo" ] = ctx . Repo
ctx . Data [ "CloneAddr" ] = ""
ctx . Data [ "Failed" ] = true
ctx . HTML ( http . StatusOK , tplMigrating )
return
}
2019-10-13 18:53:14 +05:30
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
2023-09-16 21:33:02 +05:30
ctx . Data [ "CloneAddr" ] , _ = util . SanitizeURL ( cfg . CloneAddr )
2021-07-01 00:44:53 +05:30
ctx . Data [ "Failed" ] = task . Status == structs . TaskStatusFailed
2021-04-05 21:00:52 +05:30
ctx . HTML ( http . StatusOK , tplMigrating )
2019-10-13 18:53:14 +05:30
return
}
2021-03-01 06:17:30 +05:30
if ctx . IsSigned {
// Set repo notification-status read if unread
2022-08-25 08:01:57 +05:30
if err := activities_model . SetRepoReadBy ( ctx , ctx . Repo . Repository . ID , ctx . Doer . ID ) ; err != nil {
2021-03-01 06:17:30 +05:30
ctx . ServerError ( "ReadBy" , err )
return
}
}
2021-11-10 01:27:58 +05:30
var firstUnit * unit_model . Unit
2018-11-28 16:56:14 +05:30
for _ , repoUnit := range ctx . Repo . Units {
2021-11-10 01:27:58 +05:30
if repoUnit . Type == unit_model . TypeCode {
2017-10-01 19:20:56 +05:30
return
}
2021-11-10 01:27:58 +05:30
unit , ok := unit_model . Units [ repoUnit . Type ]
2017-10-01 19:20:56 +05:30
if ok && ( firstUnit == nil || ! firstUnit . IsLessThan ( unit ) ) {
firstUnit = & unit
}
2017-05-18 20:24:24 +05:30
}
2017-10-01 19:20:56 +05:30
if firstUnit != nil {
2021-11-16 23:48:25 +05:30
ctx . Redirect ( fmt . Sprintf ( "%s%s" , ctx . Repo . Repository . Link ( ) , firstUnit . URI ) )
2017-05-18 20:24:24 +05:30
return
}
}
2018-01-11 03:04:17 +05:30
ctx . NotFound ( "Home" , fmt . Errorf ( ctx . Tr ( "units.error.no_unit_allowed_repo" ) ) )
2017-05-18 20:24:24 +05:30
}
2022-11-11 22:32:50 +05:30
func checkCitationFile ( ctx * context . Context , entry * git . TreeEntry ) {
if entry . Name ( ) != "" {
return
}
tree , err := ctx . Repo . Commit . SubTree ( ctx . Repo . TreePath )
if err != nil {
2023-09-29 13:12:39 +05:30
HandleGitError ( ctx , "Repo.Commit.SubTree" , err )
2022-11-11 22:32:50 +05:30
return
}
allEntries , err := tree . ListEntries ( )
if err != nil {
ctx . ServerError ( "ListEntries" , err )
return
}
for _ , entry := range allEntries {
if entry . Name ( ) == "CITATION.cff" || entry . Name ( ) == "CITATION.bib" {
// Read Citation file contents
2023-12-06 14:21:01 +05:30
if content , err := entry . Blob ( ) . GetBlobContent ( setting . UI . MaxDisplayFileSize ) ; err != nil {
log . Error ( "checkCitationFile: GetBlobContent: %v" , err )
} else {
ctx . Data [ "CitiationExist" ] = true
ctx . PageData [ "citationFileContent" ] = content
break
2022-11-11 22:32:50 +05:30
}
}
}
}
2021-10-08 18:38:22 +05:30
// Home render repository home page
func Home ( ctx * context . Context ) {
2023-04-23 05:08:25 +05:30
if setting . Other . EnableFeed {
2022-11-21 10:44:58 +05:30
isFeed , _ , showFeedType := feed . GetFeedType ( ctx . Params ( ":reponame" ) , ctx . Req )
if isFeed {
2023-04-25 19:38:29 +05:30
switch {
case ctx . Link == fmt . Sprintf ( "%s.%s" , ctx . Repo . RepoLink , showFeedType ) :
feed . ShowRepoFeed ( ctx , ctx . Repo . Repository , showFeedType )
case ctx . Repo . TreePath == "" :
feed . ShowBranchFeed ( ctx , ctx . Repo . Repository , showFeedType )
case ctx . Repo . TreePath != "" :
feed . ShowFileFeed ( ctx , ctx . Repo . Repository , showFeedType )
}
2022-11-21 10:44:58 +05:30
return
}
}
2022-03-13 22:10:47 +05:30
2021-10-08 18:38:22 +05:30
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 {
2023-09-29 13:12:39 +05:30
HandleGitError ( ctx , "Repo.Commit.SubTree" , err )
2021-10-08 18:38:22 +05:30
return nil
}
2021-11-16 23:48:25 +05:30
ctx . Data [ "LastCommitLoaderURL" ] = ctx . Repo . RepoLink + "/lastcommit/" + url . PathEscape ( ctx . Repo . CommitID ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2021-10-08 18:38:22 +05:30
// Get current entry user currently looking at.
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
if err != nil {
2023-09-29 13:12:39 +05:30
HandleGitError ( ctx , "Repo.Commit.GetTreeEntryByPath" , err )
2021-10-08 18:38:22 +05:30
return nil
}
if ! entry . IsDir ( ) {
2023-09-29 13:12:39 +05:30
HandleGitError ( ctx , "Repo.Commit.GetTreeEntryByPath" , err )
2021-10-08 18:38:22 +05:30
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 10:48:26 +05:30
selected := make ( container . Set [ string ] )
selected . AddMultiple ( ctx . FormStrings ( "f[]" ) ... )
2021-10-08 18:38:22 +05:30
entries := allEntries
if len ( selected ) > 0 {
entries = make ( git . Entries , 0 , len ( selected ) )
for _ , entry := range allEntries {
2022-10-12 10:48:26 +05:30
if selected . Contains ( entry . Name ( ) ) {
2021-10-08 18:38:22 +05:30
entries = append ( entries , entry )
}
}
}
var latestCommit * git . Commit
2022-07-25 21:09:42 +05:30
ctx . Data [ "Files" ] , latestCommit , err = entries . GetCommitsInfo ( commitInfoCtx , ctx . Repo . Commit , ctx . Repo . TreePath )
2021-10-08 18:38:22 +05:30
if err != nil {
ctx . ServerError ( "GetCommitsInfo" , err )
return nil
}
2024-01-15 22:12:15 +05:30
if ! loadLatestCommitData ( ctx , latestCommit ) {
return nil
2022-07-15 18:31:32 +05:30
}
2021-10-08 18:38:22 +05:30
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
treeLink := branchLink
if len ( ctx . Repo . TreePath ) > 0 {
2021-11-16 23:48:25 +05:30
treeLink += "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
2021-10-08 18:38:22 +05:30
}
ctx . Data [ "TreeLink" ] = treeLink
ctx . Data [ "SSHDomain" ] = setting . SSH . Domain
return allEntries
}
2020-02-11 15:04:17 +05:30
func renderLanguageStats ( ctx * context . Context ) {
2023-10-11 09:54:07 +05:30
langs , err := repo_model . GetTopLanguageStats ( ctx , ctx . Repo . Repository , 5 )
2020-02-11 15:04:17 +05:30
if err != nil {
ctx . ServerError ( "Repo.GetTopLanguageStats" , err )
return
}
ctx . Data [ "LanguageStats" ] = langs
}
2020-05-06 03:21:49 +05:30
func renderRepoTopics ( ctx * context . Context ) {
2023-09-16 20:09:12 +05:30
topics , _ , err := repo_model . FindTopics ( ctx , & repo_model . FindTopicOptions {
2020-05-06 03:21:49 +05:30
RepoID : ctx . Repo . Repository . ID ,
} )
if err != nil {
ctx . ServerError ( "models.FindTopics" , err )
return
}
ctx . Data [ "Topics" ] = topics
}
2017-05-18 20:24:24 +05:30
func renderCode ( ctx * context . Context ) {
2017-03-18 16:29:07 +05:30
ctx . Data [ "PageIsViewCode" ] = true
2023-04-19 19:10:42 +05:30
ctx . Data [ "RepositoryUploadEnabled" ] = setting . Repository . Upload . Enabled
2017-03-18 16:29:07 +05:30
2023-04-19 19:10:42 +05:30
if ctx . Repo . Commit == nil || ctx . Repo . Repository . IsEmpty || ctx . Repo . Repository . IsBroken ( ) {
showEmpty := true
2022-07-27 22:16:34 +05:30
var err error
if ctx . Repo . GitRepo != nil {
2023-04-19 19:10:42 +05:30
showEmpty , err = ctx . Repo . GitRepo . IsEmpty ( )
2022-07-27 22:16:34 +05:30
if err != nil {
2023-04-19 19:10:42 +05:30
log . Error ( "GitRepo.IsEmpty: %v" , err )
ctx . Repo . Repository . Status = repo_model . RepositoryBroken
showEmpty = true
ctx . Flash . Error ( ctx . Tr ( "error.occurred" ) , true )
2022-07-27 22:16:34 +05:30
}
2021-12-24 21:06:26 +05:30
}
2023-04-19 19:10:42 +05:30
if showEmpty {
2021-12-24 21:06:26 +05:30
ctx . HTML ( http . StatusOK , tplRepoEMPTY )
return
}
2023-04-19 19:10:42 +05:30
2021-12-24 21:06:26 +05:30
// 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 19:38:52 +05:30
if err = repo_model . UpdateRepositoryCols ( ctx , ctx . Repo . Repository , "is_empty" ) ; err != nil {
2021-12-24 21:06:26 +05:30
ctx . ServerError ( "UpdateRepositoryCols" , err )
return
}
2022-06-06 13:31:49 +05:30
if err = repo_module . UpdateRepoSize ( ctx , ctx . Repo . Repository ) ; err != nil {
2021-12-24 21:06:26 +05:30
ctx . ServerError ( "UpdateRepoSize" , err )
return
}
2023-04-19 19:10:42 +05:30
// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
link := ctx . Link
if ctx . Req . URL . RawQuery != "" {
link += "?" + ctx . Req . URL . RawQuery
}
ctx . Redirect ( link )
return
2017-03-18 16:29:07 +05:30
}
2016-08-30 14:38:38 +05:30
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
2018-04-11 08:21:44 +05:30
// Get Topics of this repo
2020-05-06 03:21:49 +05:30
renderRepoTopics ( ctx )
if ctx . Written ( ) {
2018-04-11 08:21:44 +05:30
return
}
2016-08-30 14:38:38 +05:30
// Get current entry user currently looking at.
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
if err != nil {
2023-09-29 13:12:39 +05:30
HandleGitError ( ctx , "Repo.Commit.GetTreeEntryByPath" , err )
2016-08-30 14:38:38 +05:30
return
}
2023-04-19 19:10:42 +05:30
checkCitationFile ( ctx , entry )
if ctx . Written ( ) {
return
2022-11-11 22:32:50 +05:30
}
2020-02-11 15:04:17 +05:30
renderLanguageStats ( ctx )
if ctx . Written ( ) {
return
}
2016-08-30 14:38:38 +05:30
if entry . IsDir ( ) {
2024-01-15 14:19:24 +05:30
renderDirectory ( ctx )
2016-08-30 14:38:38 +05:30
} else {
2024-01-15 14:19:24 +05:30
renderFile ( ctx , entry )
2016-08-30 14:38:38 +05:30
}
if ctx . Written ( ) {
return
}
2014-07-26 09:54:27 +05:30
2023-07-08 08:49:00 +05:30
if ctx . Doer != nil {
if err := ctx . Repo . Repository . GetBaseRepo ( ctx ) ; err != nil {
ctx . ServerError ( "GetBaseRepo" , err )
return
}
2023-08-01 12:55:11 +05:30
showRecentlyPushedNewBranches := true
if ctx . Repo . Repository . IsMirror ||
! ctx . Repo . Repository . UnitEnabled ( ctx , unit_model . TypePullRequests ) {
showRecentlyPushedNewBranches = false
}
if showRecentlyPushedNewBranches {
ctx . Data [ "RecentlyPushedNewBranches" ] , err = git_model . FindRecentlyPushedNewBranches ( ctx , ctx . Repo . Repository . ID , ctx . Doer . ID , ctx . Repo . Repository . DefaultBranch )
if err != nil {
ctx . ServerError ( "GetRecentlyPushedBranches" , err )
return
}
2023-07-08 08:49:00 +05:30
}
}
2016-08-30 14:38:38 +05:30
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 09:54:27 +05:30
}
ctx . Data [ "HasParentPath" ] = true
2016-08-10 01:26:00 +05:30
if len ( paths ) - 2 >= 0 {
ctx . Data [ "ParentPath" ] = "/" + paths [ len ( paths ) - 2 ]
2014-07-26 09:54:27 +05:30
}
}
2016-08-10 01:26:00 +05:30
ctx . Data [ "Paths" ] = paths
2024-01-15 14:19:24 +05:30
branchLink := ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( )
treeLink := branchLink
if len ( ctx . Repo . TreePath ) > 0 {
treeLink += "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
}
2016-08-28 03:55:01 +05:30
ctx . Data [ "TreeLink" ] = treeLink
2016-08-30 14:38:38 +05:30
ctx . Data [ "TreeNames" ] = treeNames
2014-07-26 09:54:27 +05:30
ctx . Data [ "BranchLink" ] = branchLink
2021-04-05 21:00:52 +05:30
ctx . HTML ( http . StatusOK , tplRepoHome )
2014-07-26 09:54:27 +05:30
}
2015-11-17 09:58:46 +05:30
2021-07-08 17:08:13 +05:30
// RenderUserCards render a page show users according the input template
2021-11-24 15:19:20 +05:30
func RenderUserCards ( ctx * context . Context , total int , getter func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) , tpl base . TplName ) {
2021-07-29 07:12:15 +05:30
page := ctx . FormInt ( "page" )
2015-11-17 09:58:46 +05:30
if page <= 0 {
page = 1
}
2022-06-12 21:21:54 +05:30
pager := context . NewPagination ( total , setting . ItemsPerPage , page , 5 )
2015-11-17 09:58:46 +05:30
ctx . Data [ "Page" ] = pager
2021-09-24 17:02:56 +05:30
items , err := getter ( db . ListOptions {
2021-02-04 22:53:46 +05:30
Page : pager . Paginater . Current ( ) ,
2022-06-12 21:21:54 +05:30
PageSize : setting . ItemsPerPage ,
2021-02-04 22:53:46 +05:30
} )
2015-11-17 09:58:46 +05:30
if err != nil {
2018-01-11 03:04:17 +05:30
ctx . ServerError ( "getter" , err )
2015-11-17 09:58:46 +05:30
return
}
2015-12-21 17:54:11 +05:30
ctx . Data [ "Cards" ] = items
2015-11-17 09:58:46 +05:30
2021-04-05 21:00:52 +05:30
ctx . HTML ( http . StatusOK , tpl )
2015-11-17 09:58:46 +05:30
}
2016-11-21 15:33:29 +05:30
// Watchers render repository's watch users
2016-03-11 22:26:52 +05:30
func Watchers ( ctx * context . Context ) {
2015-11-17 09:58:46 +05:30
ctx . Data [ "Title" ] = ctx . Tr ( "repo.watchers" )
2015-12-21 17:54:11 +05:30
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.watchers" )
2015-11-17 09:58:46 +05:30
ctx . Data [ "PageIsWatchers" ] = true
2019-04-20 09:45:19 +05:30
2021-12-10 06:57:50 +05:30
RenderUserCards ( ctx , ctx . Repo . Repository . NumWatches , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2023-09-15 11:43:19 +05:30
return repo_model . GetRepoWatchers ( ctx , ctx . Repo . Repository . ID , opts )
2021-12-10 06:57:50 +05:30
} , tplWatchers )
2015-11-17 09:58:46 +05:30
}
2016-11-21 15:33:29 +05:30
// Stars render repository's starred users
2016-03-11 22:26:52 +05:30
func Stars ( ctx * context . Context ) {
2015-11-17 09:58:46 +05:30
ctx . Data [ "Title" ] = ctx . Tr ( "repo.stargazers" )
2015-12-21 17:54:11 +05:30
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.stargazers" )
2015-11-17 09:58:46 +05:30
ctx . Data [ "PageIsStargazers" ] = true
2021-11-24 15:19:20 +05:30
RenderUserCards ( ctx , ctx . Repo . Repository . NumStars , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2023-09-15 11:43:19 +05:30
return repo_model . GetStargazers ( ctx , ctx . Repo . Repository , opts )
2021-11-22 20:51:55 +05:30
} , tplWatchers )
2015-11-17 09:58:46 +05:30
}
2015-11-17 10:03:40 +05:30
2016-11-21 15:33:29 +05:30
// Forks render repository's forked users
2016-03-11 22:26:52 +05:30
func Forks ( ctx * context . Context ) {
2023-04-18 02:11:06 +05:30
ctx . Data [ "Title" ] = ctx . Tr ( "repo.forks" )
2015-11-17 10:03:40 +05:30
2021-11-18 20:15:56 +05:30
page := ctx . FormInt ( "page" )
if page <= 0 {
page = 1
}
2022-06-12 21:21:54 +05:30
pager := context . NewPagination ( ctx . Repo . Repository . NumForks , setting . ItemsPerPage , page , 5 )
2021-11-18 20:15:56 +05:30
ctx . Data [ "Page" ] = pager
2023-09-14 22:39:32 +05:30
forks , err := repo_model . GetForks ( ctx , ctx . Repo . Repository , db . ListOptions {
2021-11-18 20:15:56 +05:30
Page : pager . Paginater . Current ( ) ,
2022-06-12 21:21:54 +05:30
PageSize : setting . ItemsPerPage ,
2021-11-18 20:15:56 +05:30
} )
2015-11-17 10:03:40 +05:30
if err != nil {
2018-01-11 03:04:17 +05:30
ctx . ServerError ( "GetForks" , err )
2015-11-17 10:03:40 +05:30
return
}
for _ , fork := range forks {
2023-02-18 17:41:03 +05:30
if err = fork . LoadOwner ( ctx ) ; err != nil {
ctx . ServerError ( "LoadOwner" , err )
2015-11-17 10:03:40 +05:30
return
}
}
2021-11-18 20:15:56 +05:30
2015-11-17 10:03:40 +05:30
ctx . Data [ "Forks" ] = forks
2021-04-05 21:00:52 +05:30
ctx . HTML ( http . StatusOK , tplForks )
2015-11-17 10:03:40 +05:30
}