2017-09-14 08:51:32 +02:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2014-07-26 00:24:27 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-07-26 00:24:27 -04:00
package repo
import (
2021-10-08 14:08:22 +01:00
gocontext "context"
2024-09-09 22:23:07 -04:00
"errors"
2016-08-09 12:56:00 -07:00
"fmt"
2023-12-17 22:38:54 +08:00
"html/template"
2021-01-23 01:49:13 +08:00
"io"
2021-04-05 17:30:52 +02:00
"net/http"
2019-10-13 21:23:14 +08:00
"net/url"
2025-07-01 00:55:36 +02:00
"path"
2014-07-26 00:24:27 -04:00
"strings"
2021-10-08 14:08:22 +01:00
"time"
2014-07-26 00:24:27 -04:00
2023-07-31 07:04:45 +02:00
_ "image/gif" // for processing gif images
_ "image/jpeg" // for processing jpeg images
_ "image/png" // for processing png images
2022-08-25 10:31:57 +08:00
activities_model "code.gitea.io/gitea/models/activities"
admin_model "code.gitea.io/gitea/models/admin"
2021-12-10 16:14:24 +08:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
2021-09-24 19:32:56 +08:00
"code.gitea.io/gitea/models/db"
2022-06-12 23:51:54 +08:00
git_model "code.gitea.io/gitea/models/git"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
unit_model "code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08: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 09:07:28 -03:00
"code.gitea.io/gitea/modules/charset"
2025-04-07 03:35:08 +08:00
"code.gitea.io/gitea/modules/fileicon"
2019-03-27 17:33:00 +08:00
"code.gitea.io/gitea/modules/git"
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 15:01:08 +08:00
"code.gitea.io/gitea/modules/markup"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2021-06-30 20:14:53 +01:00
"code.gitea.io/gitea/modules/structs"
2024-12-22 23:33:19 +08:00
"code.gitea.io/gitea/modules/templates"
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"
2025-02-16 04:24:07 -08:00
asymkey_service "code.gitea.io/gitea/services/asymkey"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2024-10-02 04:25:08 +09:00
repo_service "code.gitea.io/gitea/services/repository"
2023-07-31 07:04:45 +02:00
_ "golang.org/x/image/bmp" // for processing bmp images
_ "golang.org/x/image/webp" // for processing webp images
2014-07-26 00:24:27 -04:00
)
const (
2025-03-15 16:26:49 +08:00
tplRepoEMPTY templates . TplName = "repo/empty"
tplRepoHome templates . TplName = "repo/home"
tplRepoView templates . TplName = "repo/view"
tplRepoViewContent templates . TplName = "repo/view_content"
tplRepoViewList templates . TplName = "repo/view_list"
tplWatchers templates . TplName = "repo/watchers"
tplForks templates . TplName = "repo/forks"
tplMigrating templates . TplName = "repo/migrate/migrating"
2014-07-26 00:24:27 -04:00
)
2022-12-14 18:11:11 +08:00
type fileInfo struct {
2025-10-16 15:12:54 +05:30
blobOrLfsSize int64
lfsMeta * lfs . Pointer
st typesniffer . SniffedType
2022-12-14 18:11:11 +08:00
}
2016-08-30 02:08:38 -07:00
2025-06-30 16:12:25 +08:00
func ( fi * fileInfo ) isLFSFile ( ) bool {
return fi . lfsMeta != nil && fi . lfsMeta . Oid != ""
}
func getFileReader ( ctx gocontext . Context , repoID int64 , blob * git . Blob ) ( buf [ ] byte , dataRc io . ReadCloser , fi * fileInfo , err error ) {
dataRc , err = blob . DataAsync ( )
2022-04-27 04:31:15 +08:00
if err != nil {
2022-12-14 18:11:11 +08:00
return nil , nil , nil , err
2022-04-27 04:31:15 +08:00
}
2025-06-30 16:12:25 +08:00
const prefetchSize = lfs . MetaFileMaxSize
buf = make ( [ ] byte , prefetchSize )
2022-04-27 04:31:15 +08:00
n , _ := util . ReadAtMost ( dataRc , buf )
buf = buf [ : n ]
2025-10-16 15:12:54 +05:30
fi = & fileInfo { blobOrLfsSize : blob . Size ( ) , st : typesniffer . DetectContentType ( buf ) }
2022-04-27 04:31:15 +08:00
// FIXME: what happens when README file is an image?
2025-06-30 16:12:25 +08:00
if ! fi . st . IsText ( ) || ! setting . LFS . StartServer {
return buf , dataRc , fi , nil
2022-12-14 18:11:11 +08:00
}
2022-04-27 04:31:15 +08:00
2022-12-14 18:11:11 +08:00
pointer , _ := lfs . ReadPointerFromBuffer ( buf )
2025-06-30 16:12:25 +08:00
if ! pointer . IsValid ( ) { // fallback to a plain file
return buf , dataRc , fi , nil
2022-12-14 18:11:11 +08:00
}
2019-02-21 20:57:16 +00:00
2023-07-24 05:47:27 +02:00
meta , err := git_model . GetLFSMetaObjectByOid ( ctx , repoID , pointer . Oid )
2025-06-30 16:12:25 +08:00
if err != nil { // fallback to a plain file
2025-11-10 13:31:25 +08:00
fi . lfsMeta = & pointer
2024-08-14 16:50:09 -05:00
log . Warn ( "Unable to access LFS pointer %s in repo %d: %v" , pointer . Oid , repoID , err )
2025-06-30 16:12:25 +08:00
return buf , dataRc , fi , nil
2022-12-14 18:11:11 +08:00
}
2019-02-21 20:57:16 +00:00
2025-06-30 16:12:25 +08:00
// close the old dataRc and open the real LFS target
_ = dataRc . Close ( )
2022-12-14 18:11:11 +08:00
dataRc , err = lfs . ReadMetaObject ( pointer )
if err != nil {
return nil , nil , nil , err
2022-04-27 04:31:15 +08:00
}
2019-02-21 20:57:16 +00:00
2025-06-30 16:12:25 +08:00
buf = make ( [ ] byte , prefetchSize )
2022-12-14 18:11:11 +08:00
n , err = util . ReadAtMost ( dataRc , buf )
if err != nil {
2025-06-30 16:12:25 +08:00
_ = dataRc . Close ( )
return nil , nil , fi , err
2022-12-14 18:11:11 +08:00
}
buf = buf [ : n ]
2025-06-30 16:12:25 +08:00
fi . st = typesniffer . DetectContentType ( buf )
2025-10-16 15:12:54 +05:30
fi . blobOrLfsSize = meta . Pointer . Size
2025-06-30 16:12:25 +08:00
fi . lfsMeta = & meta . Pointer
return buf , dataRc , fi , nil
2022-12-14 18:11:11 +08:00
}
2024-01-15 17:42:15 +01:00
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 {
2025-02-16 04:24:07 -08:00
verification := asymkey_service . ParseCommitWithSignature ( ctx , latestCommit )
2024-01-15 17:42:15 +01:00
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 )
2025-05-27 03:00:22 +08:00
statuses , err := git_model . GetLatestCommitStatus ( ctx , ctx . Repo . Repository . ID , latestCommit . ID . String ( ) , db . ListOptionsAll )
2024-01-15 17:42:15 +01:00
if err != nil {
log . Error ( "GetLatestCommitStatus: %v" , err )
}
2024-07-28 23:11:40 +08:00
if ! ctx . Repo . CanRead ( unit_model . TypeActions ) {
git_model . CommitStatusesHideActionsURL ( ctx , statuses )
}
2024-01-15 17:42:15 +01:00
ctx . Data [ "LatestCommitStatus" ] = git_model . CalcCommitStatus ( statuses )
ctx . Data [ "LatestCommitStatuses" ] = statuses
}
return true
}
2026-01-26 10:34:38 +08:00
func markupRenderToHTML ( ctx * context . Context , renderCtx * markup . RenderContext , renderer markup . Renderer , input io . Reader ) ( escaped * charset . EscapeStatus , output template . HTML , err error ) {
2022-08-13 19:32:34 +01:00
markupRd , markupWr := io . Pipe ( )
defer markupWr . Close ( )
2025-10-23 07:41:38 +08:00
2022-08-13 19:32:34 +01:00
done := make ( chan struct { } )
go func ( ) {
sb := & strings . Builder { }
2025-10-23 07:41:38 +08:00
if markup . RendererNeedPostProcess ( renderer ) {
2026-04-06 13:07:33 +02:00
escaped , _ = charset . EscapeControlReader ( markupRd , sb , ctx . Locale , charset . EscapeOptionsForView ( ) )
2025-10-23 07:41:38 +08:00
} else {
escaped = & charset . EscapeStatus { }
_ , _ = io . Copy ( sb , markupRd )
}
2023-12-17 22:38:54 +08:00
output = template . HTML ( sb . String ( ) )
2022-08-13 19:32:34 +01:00
close ( done )
} ( )
2025-10-23 07:41:38 +08:00
err = markup . RenderWithRenderer ( renderCtx , renderer , input , markupWr )
2022-08-13 19:32:34 +01:00
_ = markupWr . CloseWithError ( err )
<- done
return escaped , output , err
}
2021-10-08 14:08:22 +01:00
func checkHomeCodeViewable ( ctx * context . Context ) {
2024-04-17 23:58:37 +08:00
if ctx . Repo . HasUnits ( ) {
2019-10-13 21:23:14 +08:00
if ctx . Repo . Repository . IsBeingCreated ( ) {
2023-09-16 16:39:12 +02:00
task , err := admin_model . GetMigratingTask ( ctx , ctx . Repo . Repository . ID )
2019-10-13 21:23:14 +08:00
if err != nil {
2022-08-25 10:31:57 +08:00
if admin_model . IsErrTaskDoesNotExist ( err ) {
2021-11-13 11:28:50 +00:00
ctx . Data [ "Repo" ] = ctx . Repo
ctx . Data [ "CloneAddr" ] = ""
ctx . Data [ "Failed" ] = true
ctx . HTML ( http . StatusOK , tplMigrating )
return
}
2019-10-13 21:23:14 +08: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
2023-09-16 18:03:02 +02:00
ctx . Data [ "CloneAddr" ] , _ = util . SanitizeURL ( cfg . CloneAddr )
2021-06-30 20:14:53 +01:00
ctx . Data [ "Failed" ] = task . Status == structs . TaskStatusFailed
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplMigrating )
2019-10-13 21:23:14 +08:00
return
}
2021-03-01 01:47:30 +01:00
if ctx . IsSigned {
// Set repo notification-status read if unread
2022-08-25 10:31:57 +08: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-10 03:57:58 +08:00
var firstUnit * unit_model . Unit
2024-04-17 16:31:37 +08:00
for _ , repoUnitType := range ctx . Repo . Permission . ReadableUnitTypes ( ) {
if repoUnitType == unit_model . TypeCode {
2024-04-17 23:58:37 +08:00
// we are doing this check in "code" unit related pages, so if the code unit is readable, no need to do any further redirection
2017-10-01 15:50:56 +02:00
return
}
2024-04-17 16:31:37 +08:00
unit , ok := unit_model . Units [ repoUnitType ]
2017-10-01 15:50:56 +02:00
if ok && ( firstUnit == nil || ! firstUnit . IsLessThan ( unit ) ) {
firstUnit = & unit
}
2017-05-18 22:54:24 +08:00
}
2017-10-01 15:50:56 +02:00
if firstUnit != nil {
2021-11-16 18:18:25 +00:00
ctx . Redirect ( fmt . Sprintf ( "%s%s" , ctx . Repo . Repository . Link ( ) , firstUnit . URI ) )
2017-05-18 22:54:24 +08:00
return
}
}
2025-02-17 14:13:17 +08:00
ctx . NotFound ( errors . New ( ctx . Locale . TrString ( "units.error.no_unit_allowed_repo" ) ) )
2017-05-18 22:54:24 +08:00
}
2021-10-08 14:08:22 +01:00
// 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
}
2025-11-30 06:58:15 +03:00
// The "/lastcommit/" endpoint is used to render the embedded HTML content for the directory file listing with latest commit info
// It needs to construct correct links to the file items, but the route only accepts a commit ID, not a full ref name (branch or tag).
// So we need to get the ref name from the query parameter "refSubUrl".
// TODO: LAST-COMMIT-ASYNC-LOADING: it needs more tests to cover this
refSubURL := path . Clean ( ctx . FormString ( "refSubUrl" ) )
prepareRepoViewContent ( ctx , util . IfZero ( refSubURL , ctx . Repo . RefTypeNameSubURL ( ) ) )
2021-10-08 14:08:22 +01:00
renderDirectoryFiles ( ctx , 0 )
if ctx . Written ( ) {
return
}
ctx . HTML ( http . StatusOK , tplRepoViewList )
}
2025-04-07 03:35:08 +08:00
func prepareDirectoryFileIcons ( ctx * context . Context , files [ ] git . CommitInfo ) {
renderedIconPool := fileicon . NewRenderedIconPool ( )
fileIcons := map [ string ] template . HTML { }
for _ , f := range files {
2025-07-01 00:55:36 +02:00
fullPath := path . Join ( ctx . Repo . TreePath , f . Entry . Name ( ) )
entryInfo := fileicon . EntryInfoFromGitTreeEntry ( ctx . Repo . Commit , fullPath , f . Entry )
fileIcons [ f . Entry . Name ( ) ] = fileicon . RenderEntryIconHTML ( renderedIconPool , entryInfo )
2025-04-07 03:35:08 +08:00
}
2025-04-29 10:51:32 +08:00
fileIcons [ ".." ] = fileicon . RenderEntryIconHTML ( renderedIconPool , fileicon . EntryInfoFolder ( ) )
2025-04-07 03:35:08 +08:00
ctx . Data [ "FileIcons" ] = fileIcons
ctx . Data [ "FileIconPoolHTML" ] = renderedIconPool . RenderToHTML ( )
}
2021-10-08 14:08:22 +01:00
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 16:42:39 +09:00
HandleGitError ( ctx , "Repo.Commit.SubTree" , err )
2021-10-08 14:08:22 +01:00
return nil
}
2025-11-30 06:58:15 +03:00
// TODO: LAST-COMMIT-ASYNC-LOADING: search this keyword to see more details
lastCommitLoaderURL := ctx . Repo . RepoLink + "/lastcommit/" + url . PathEscape ( ctx . Repo . CommitID ) + "/" + util . PathEscapeSegments ( ctx . Repo . TreePath )
ctx . Data [ "LastCommitLoaderURL" ] = lastCommitLoaderURL + "?refSubUrl=" + url . QueryEscape ( ctx . Repo . RefTypeNameSubURL ( ) )
2021-10-08 14:08:22 +01:00
// Get current entry user currently looking at.
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( ctx . Repo . TreePath )
if err != nil {
2023-09-29 16:42:39 +09:00
HandleGitError ( ctx , "Repo.Commit.GetTreeEntryByPath" , err )
2021-10-08 14:08:22 +01:00
return nil
}
if ! entry . IsDir ( ) {
2023-09-29 16:42:39 +09:00
HandleGitError ( ctx , "Repo.Commit.GetTreeEntryByPath" , err )
2021-10-08 14:08:22 +01:00
return nil
}
allEntries , err := tree . ListEntries ( )
if err != nil {
ctx . ServerError ( "ListEntries" , err )
return nil
}
2025-11-06 01:48:38 +08:00
allEntries . CustomSort ( base . NaturalSortCompare )
2021-10-08 14:08:22 +01:00
commitInfoCtx := gocontext . Context ( ctx )
if timeout > 0 {
var cancel gocontext . CancelFunc
commitInfoCtx , cancel = gocontext . WithTimeout ( ctx , timeout )
defer cancel ( )
}
2025-07-14 23:28:34 +08:00
files , latestCommit , err := allEntries . GetCommitsInfo ( commitInfoCtx , ctx . Repo . RepoLink , ctx . Repo . Commit , ctx . Repo . TreePath )
2021-10-08 14:08:22 +01:00
if err != nil {
ctx . ServerError ( "GetCommitsInfo" , err )
return nil
}
2025-11-30 06:58:15 +03:00
{
if timeout != 0 && ! setting . IsProd && ! setting . IsInTesting {
log . Debug ( "first call to get directory file commit info" )
clearFilesCommitInfo := func ( ) {
log . Warn ( "clear directory file commit info to force async loading on frontend" )
for i := range files {
files [ i ] . Commit = nil
}
}
_ = clearFilesCommitInfo
// clearFilesCommitInfo() // TODO: LAST-COMMIT-ASYNC-LOADING: debug the frontend async latest commit info loading, uncomment this line, and it needs more tests
}
}
2024-03-02 21:31:59 +02:00
ctx . Data [ "Files" ] = files
2025-04-07 03:35:08 +08:00
prepareDirectoryFileIcons ( ctx , files )
2024-03-02 21:31:59 +02:00
for _ , f := range files {
if f . Commit == nil {
ctx . Data [ "HasFilesWithoutLatestCommit" ] = true
break
}
}
2021-10-08 14:08:22 +01:00
2024-01-15 17:42:15 +01:00
if ! loadLatestCommitData ( ctx , latestCommit ) {
return nil
2022-07-15 15:01:32 +02:00
}
2021-10-08 14:08:22 +01:00
return allEntries
}
2021-07-08 07:38:13 -04:00
// RenderUserCards render a page show users according the input template
2024-12-22 23:33:19 +08:00
func RenderUserCards ( ctx * context . Context , total int , getter func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) , tpl templates . TplName ) {
2021-07-29 09:42:15 +08:00
page := ctx . FormInt ( "page" )
2015-11-16 23:28:46 -05:00
if page <= 0 {
page = 1
}
2026-03-08 15:35:50 +01:00
pager := context . NewPagination ( int64 ( total ) , setting . ItemsPerPage , page , 5 )
2015-11-16 23:28:46 -05:00
ctx . Data [ "Page" ] = pager
2021-09-24 19:32:56 +08:00
items , err := getter ( db . ListOptions {
2021-02-05 01:23:46 +08:00
Page : pager . Paginater . Current ( ) ,
2022-06-12 23:51:54 +08:00
PageSize : setting . ItemsPerPage ,
2021-02-05 01:23:46 +08:00
} )
2015-11-16 23:28:46 -05:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx . ServerError ( "getter" , err )
2015-11-16 23:28:46 -05:00
return
}
2015-12-21 04:24:11 -08:00
ctx . Data [ "Cards" ] = items
2015-11-16 23:28:46 -05:00
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tpl )
2015-11-16 23:28:46 -05:00
}
2016-11-21 18:03:29 +08:00
// Watchers render repository's watch users
2016-03-11 11:56:52 -05:00
func Watchers ( ctx * context . Context ) {
2015-11-16 23:28:46 -05:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.watchers" )
2015-12-21 04:24:11 -08:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.watchers" )
2021-12-10 09:27:50 +08:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumWatches , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2023-09-15 08:13:19 +02:00
return repo_model . GetRepoWatchers ( ctx , ctx . Repo . Repository . ID , opts )
2021-12-10 09:27:50 +08:00
} , tplWatchers )
2015-11-16 23:28:46 -05:00
}
2016-11-21 18:03:29 +08:00
// Stars render repository's starred users
2016-03-11 11:56:52 -05:00
func Stars ( ctx * context . Context ) {
2015-11-16 23:28:46 -05:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.stargazers" )
2015-12-21 04:24:11 -08:00
ctx . Data [ "CardsTitle" ] = ctx . Tr ( "repo.stargazers" )
2021-11-24 17:49:20 +08:00
RenderUserCards ( ctx , ctx . Repo . Repository . NumStars , func ( opts db . ListOptions ) ( [ ] * user_model . User , error ) {
2023-09-15 08:13:19 +02:00
return repo_model . GetStargazers ( ctx , ctx . Repo . Repository , opts )
2021-11-22 23:21:55 +08:00
} , tplWatchers )
2015-11-16 23:28:46 -05:00
}
2015-11-16 23:33:40 -05:00
2016-11-21 18:03:29 +08:00
// Forks render repository's forked users
2016-03-11 11:56:52 -05:00
func Forks ( ctx * context . Context ) {
2023-04-17 15:41:06 -05:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.forks" )
2015-11-16 23:33:40 -05:00
2021-11-18 14:45:56 +00:00
page := ctx . FormInt ( "page" )
if page <= 0 {
page = 1
}
2024-11-17 19:06:25 -08:00
pageSize := setting . ItemsPerPage
2021-11-18 14:45:56 +00:00
2024-11-17 19:06:25 -08:00
forks , total , err := repo_service . FindForks ( ctx , ctx . Repo . Repository , ctx . Doer , db . ListOptions {
Page : page ,
PageSize : pageSize ,
2021-11-18 14:45:56 +00:00
} )
2015-11-16 23:33:40 -05:00
if err != nil {
2024-11-17 19:06:25 -08:00
ctx . ServerError ( "FindForks" , err )
2015-11-16 23:33:40 -05:00
return
}
2024-11-17 19:06:25 -08:00
if err := repo_model . RepositoryList ( forks ) . LoadOwners ( ctx ) ; err != nil {
ctx . ServerError ( "LoadAttributes" , err )
return
2015-11-16 23:33:40 -05:00
}
2021-11-18 14:45:56 +00:00
2026-03-08 15:35:50 +01:00
pager := context . NewPagination ( total , pageSize , page , 5 )
2025-06-22 03:27:25 +08:00
ctx . Data [ "ShowRepoOwnerAvatar" ] = true
ctx . Data [ "ShowRepoOwnerOnList" ] = true
2024-11-17 19:06:25 -08:00
ctx . Data [ "Page" ] = pager
2025-06-22 03:27:25 +08:00
ctx . Data [ "Repos" ] = forks
2015-11-16 23:33:40 -05:00
2021-04-05 17:30:52 +02:00
ctx . HTML ( http . StatusOK , tplForks )
2015-11-16 23:33:40 -05:00
}