mirror of
https://github.com/go-gitea/gitea
synced 2024-12-27 02:54:27 +00:00
352 lines
10 KiB
Go
352 lines
10 KiB
Go
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
package repo
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"html/template"
|
||
|
"net/http"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"code.gitea.io/gitea/models/db"
|
||
|
git_model "code.gitea.io/gitea/models/git"
|
||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||
|
"code.gitea.io/gitea/modules/git"
|
||
|
"code.gitea.io/gitea/modules/log"
|
||
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||
|
"code.gitea.io/gitea/modules/setting"
|
||
|
"code.gitea.io/gitea/modules/svg"
|
||
|
"code.gitea.io/gitea/modules/util"
|
||
|
"code.gitea.io/gitea/routers/web/feed"
|
||
|
"code.gitea.io/gitea/services/context"
|
||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||
|
)
|
||
|
|
||
|
func checkOutdatedBranch(ctx *context.Context) {
|
||
|
if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
|
||
|
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
|
||
|
if err != nil {
|
||
|
log.Error("GetBranchCommitID: %v", err)
|
||
|
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||
|
return
|
||
|
}
|
||
|
|
||
|
dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
|
||
|
if err != nil {
|
||
|
log.Error("GetBranch: %v", err)
|
||
|
// Don't return an error page, as it can be rechecked the next time the user opens the page.
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if dbBranch.CommitID != commit.ID.String() {
|
||
|
ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func prepareHomeSidebarRepoTopics(ctx *context.Context) {
|
||
|
topics, err := db.Find[repo_model.Topic](ctx, &repo_model.FindTopicOptions{
|
||
|
RepoID: ctx.Repo.Repository.ID,
|
||
|
})
|
||
|
if err != nil {
|
||
|
ctx.ServerError("models.FindTopics", err)
|
||
|
return
|
||
|
}
|
||
|
ctx.Data["Topics"] = topics
|
||
|
}
|
||
|
|
||
|
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||
|
var tmplApps []map[string]any
|
||
|
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||
|
if len(apps) == 0 {
|
||
|
apps = setting.DefaultOpenWithEditorApps()
|
||
|
}
|
||
|
for _, app := range apps {
|
||
|
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||
|
var iconHTML template.HTML
|
||
|
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
|
||
|
iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
|
||
|
} else {
|
||
|
iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
|
||
|
}
|
||
|
tmplApps = append(tmplApps, map[string]any{
|
||
|
"DisplayName": app.DisplayName,
|
||
|
"OpenURL": app.OpenURL,
|
||
|
"IconHTML": iconHTML,
|
||
|
})
|
||
|
}
|
||
|
ctx.Data["OpenWithEditorApps"] = tmplApps
|
||
|
}
|
||
|
|
||
|
func prepareHomeSidebarCitationFile(ctx *context.Context, entry *git.TreeEntry) {
|
||
|
if entry.Name() != "" {
|
||
|
return
|
||
|
}
|
||
|
tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
|
||
|
if err != nil {
|
||
|
HandleGitError(ctx, "Repo.Commit.SubTree", err)
|
||
|
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
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func prepareHomeSidebarLicenses(ctx *context.Context) {
|
||
|
repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
|
||
|
if err != nil {
|
||
|
ctx.ServerError("GetRepoLicenses", err)
|
||
|
return
|
||
|
}
|
||
|
ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
|
||
|
ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
|
||
|
}
|
||
|
|
||
|
func prepareToRenderDirectory(ctx *context.Context) {
|
||
|
entries := renderDirectoryFiles(ctx, 1*time.Second)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if ctx.Repo.TreePath != "" {
|
||
|
ctx.Data["HideRepoInfo"] = true
|
||
|
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
|
||
|
}
|
||
|
|
||
|
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
|
||
|
if err != nil {
|
||
|
ctx.ServerError("findReadmeFileInEntries", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
prepareToRenderReadmeFile(ctx, subfolder, readmeFile)
|
||
|
}
|
||
|
|
||
|
func prepareHomeSidebarLanguageStats(ctx *context.Context) {
|
||
|
langs, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5)
|
||
|
if err != nil {
|
||
|
ctx.ServerError("Repo.GetTopLanguageStats", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
ctx.Data["LanguageStats"] = langs
|
||
|
}
|
||
|
|
||
|
func prepareHomeSidebarLatestRelease(ctx *context.Context) {
|
||
|
if !ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeReleases) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
|
||
|
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||
|
ctx.ServerError("GetLatestReleaseByRepoID", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if release != nil {
|
||
|
if err = release.LoadAttributes(ctx); err != nil {
|
||
|
ctx.ServerError("release.LoadAttributes", err)
|
||
|
return
|
||
|
}
|
||
|
ctx.Data["LatestRelease"] = release
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func renderHomeCode(ctx *context.Context) {
|
||
|
ctx.Data["PageIsViewCode"] = true
|
||
|
ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
|
||
|
prepareOpenWithEditorApps(ctx)
|
||
|
|
||
|
if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
|
||
|
showEmpty := true
|
||
|
var err error
|
||
|
if ctx.Repo.GitRepo != nil {
|
||
|
showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
|
||
|
if err != nil {
|
||
|
log.Error("GitRepo.IsEmpty: %v", err)
|
||
|
ctx.Repo.Repository.Status = repo_model.RepositoryBroken
|
||
|
showEmpty = true
|
||
|
ctx.Flash.Error(ctx.Tr("error.occurred"), true)
|
||
|
}
|
||
|
}
|
||
|
if showEmpty {
|
||
|
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
|
||
|
if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
|
||
|
ctx.ServerError("UpdateRepositoryCols", err)
|
||
|
return
|
||
|
}
|
||
|
if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
|
||
|
ctx.ServerError("UpdateRepoSize", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
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
|
||
|
|
||
|
// Get Topics of this repo
|
||
|
prepareHomeSidebarRepoTopics(ctx)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Get current entry user currently looking at.
|
||
|
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
|
||
|
if err != nil {
|
||
|
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
checkOutdatedBranch(ctx)
|
||
|
|
||
|
if entry.IsDir() {
|
||
|
prepareToRenderDirectory(ctx)
|
||
|
} else {
|
||
|
renderFile(ctx, entry)
|
||
|
}
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if ctx.Doer != nil {
|
||
|
if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
|
||
|
ctx.ServerError("GetBaseRepo", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
opts := &git_model.FindRecentlyPushedNewBranchesOptions{
|
||
|
Repo: ctx.Repo.Repository,
|
||
|
BaseRepo: ctx.Repo.Repository,
|
||
|
}
|
||
|
if ctx.Repo.Repository.IsFork {
|
||
|
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
|
||
|
}
|
||
|
|
||
|
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
|
||
|
if err != nil {
|
||
|
ctx.ServerError("GetUserRepoPermission", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
|
||
|
opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
|
||
|
baseRepoPerm.CanRead(unit_model.TypePullRequests) {
|
||
|
ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
|
||
|
if err != nil {
|
||
|
log.Error("FindRecentlyPushedNewBranches failed: %v", err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var treeNames, paths []string
|
||
|
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||
|
treeLink := branchLink
|
||
|
if ctx.Repo.TreePath != "" {
|
||
|
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||
|
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]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
isTreePathRoot := ctx.Repo.TreePath == ""
|
||
|
if isTreePathRoot {
|
||
|
prepareHomeSidebarLicenses(ctx)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
prepareHomeSidebarCitationFile(ctx, entry)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
prepareHomeSidebarLanguageStats(ctx)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
prepareHomeSidebarLatestRelease(ctx)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctx.Data["Paths"] = paths
|
||
|
ctx.Data["TreeLink"] = treeLink
|
||
|
ctx.Data["TreeNames"] = treeNames
|
||
|
ctx.Data["BranchLink"] = branchLink
|
||
|
ctx.HTML(http.StatusOK, tplRepoHome)
|
||
|
}
|
||
|
|
||
|
// Home render repository home page
|
||
|
func Home(ctx *context.Context) {
|
||
|
if setting.Other.EnableFeed {
|
||
|
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
|
||
|
if isFeed {
|
||
|
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)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
checkHomeCodeViewable(ctx)
|
||
|
if ctx.Written() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
renderHomeCode(ctx)
|
||
|
}
|