1
1
mirror of https://github.com/go-gitea/gitea synced 2024-06-02 17:35:49 +00:00
gitea/routers/repo/wiki.go
zeripath fb5af37b3e
Add Close() method to gogitRepository (#8901) (#8958)
Backport #8901 - Adjusted slightly for 1.9

In investigating #7947 it has become clear that the storage component of go-git repositories needs closing.

This PR adds this Close function and adds the Close functions as necessary.

In TransferOwnership the ctx.Repo.GitRepo is closed if it is open to help prevent the risk of multiple open files.

Fixes #7947
2019-11-13 18:51:33 +00:00

485 lines
12 KiB
Go

// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea 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 (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/util"
)
const (
tplWikiStart base.TplName = "repo/wiki/start"
tplWikiView base.TplName = "repo/wiki/view"
tplWikiNew base.TplName = "repo/wiki/new"
tplWikiPages base.TplName = "repo/wiki/pages"
)
// MustEnableWiki check if wiki is enabled, if external then redirect
func MustEnableWiki(ctx *context.Context) {
if !ctx.Repo.CanRead(models.UnitTypeWiki) &&
!ctx.Repo.CanRead(models.UnitTypeExternalWiki) {
if log.IsTrace() {
log.Trace("Permission Denied: User %-v cannot read %-v or %-v of repo %-v\n"+
"User in repo has Permissions: %-+v",
ctx.User,
models.UnitTypeWiki,
models.UnitTypeExternalWiki,
ctx.Repo.Repository,
ctx.Repo.Permission)
}
ctx.NotFound("MustEnableWiki", nil)
return
}
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalWiki)
if err == nil {
ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
return
}
}
// PageMeta wiki page meat information
type PageMeta struct {
Name string
SubURL string
UpdatedUnix util.TimeStamp
}
// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entries, err := commit.ListEntries()
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == target {
return entry, nil
}
}
return nil, nil
}
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil, nil, err
}
commit, err := wikiRepo.GetBranchCommit("master")
if err != nil {
return wikiRepo, nil, err
}
return wikiRepo, commit, nil
}
// wikiContentsByEntry returns the contents of the wiki page referenced by the
// given tree entry. Writes to ctx if an error occurs.
func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
reader, err := entry.Blob().DataAsync()
if err != nil {
ctx.ServerError("Blob.Data", err)
return nil
}
defer reader.Close()
content, err := ioutil.ReadAll(reader)
if err != nil {
ctx.ServerError("ReadAll", err)
return nil
}
return content
}
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, bool) {
entry, err := findEntryForFile(commit, models.WikiNameToFilename(wikiName))
if err != nil {
ctx.ServerError("findEntryForFile", err)
return nil, false
} else if entry == nil {
return nil, false
}
return wikiContentsByEntry(ctx, entry), true
}
func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *git.TreeEntry) {
wikiRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommit", err)
}
return nil, nil
}
// Get page list.
if isViewPage {
entries, err := commit.ListEntries()
if err != nil {
ctx.ServerError("ListEntries", err)
return nil, nil
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
wikiRepo.Close()
ctx.ServerError("WikiFilenameToName", err)
return nil, nil
} else if wikiName == "_Sidebar" || wikiName == "_Footer" {
continue
}
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: models.WikiNameToSubURL(wikiName),
})
}
ctx.Data["Pages"] = pages
}
pageName := models.NormalizeWikiName(ctx.Params(":page"))
if len(pageName) == 0 {
pageName = "Home"
}
ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
ctx.Data["old_title"] = pageName
ctx.Data["Title"] = pageName
ctx.Data["title"] = pageName
ctx.Data["RequireHighlightJS"] = true
pageFilename := models.WikiNameToFilename(pageName)
var entry *git.TreeEntry
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
wikiRepo.Close()
ctx.ServerError("findEntryForFile", err)
return nil, nil
} else if entry == nil {
wikiRepo.Close()
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
return nil, nil
}
data := wikiContentsByEntry(ctx, entry)
if ctx.Written() {
if wikiRepo != nil {
wikiRepo.Close()
}
return nil, nil
}
if isViewPage {
sidebarContent, sidebarPresent := wikiContentsByName(ctx, commit, "_Sidebar")
if ctx.Written() {
return nil, nil
}
footerContent, footerPresent := wikiContentsByName(ctx, commit, "_Footer")
if ctx.Written() {
return nil, nil
}
metas := ctx.Repo.Repository.ComposeMetas()
ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
ctx.Data["sidebarPresent"] = sidebarPresent
ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
ctx.Data["footerPresent"] = footerPresent
ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
} else {
ctx.Data["content"] = string(data)
ctx.Data["sidebarPresent"] = false
ctx.Data["sidebarContent"] = ""
ctx.Data["footerPresent"] = false
ctx.Data["footerContent"] = ""
}
return wikiRepo, entry
}
// Wiki renders single wiki page
func Wiki(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
if !ctx.Repo.Repository.HasWiki() {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(200, tplWikiStart)
return
}
wikiRepo, entry := renderWikiPage(ctx, true)
if ctx.Written() {
if wikiRepo != nil {
wikiRepo.Close()
}
return
}
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
if entry == nil {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(200, tplWikiStart)
return
}
wikiPath := entry.Name()
if markup.Type(wikiPath) != markdown.MarkupName {
ext := strings.ToUpper(filepath.Ext(wikiPath))
ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
}
// Get last change information.
lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
if err != nil {
ctx.ServerError("GetCommitByPath", err)
return
}
ctx.Data["Author"] = lastCommit.Author
ctx.HTML(200, tplWikiView)
}
// WikiPages render wiki pages list page
func WikiPages(ctx *context.Context) {
if !ctx.Repo.Repository.HasWiki() {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
return
}
ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
ctx.Data["PageIsWiki"] = true
ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
wikiRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if wikiRepo != nil {
wikiRepo.Close()
}
return
}
entries, err := commit.ListEntries()
if err != nil {
if wikiRepo != nil {
wikiRepo.Close()
}
ctx.ServerError("ListEntries", err)
return
}
pages := make([]PageMeta, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
c, err := wikiRepo.GetCommitByPath(entry.Name())
if err != nil {
if wikiRepo != nil {
wikiRepo.Close()
}
ctx.ServerError("GetCommit", err)
return
}
wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
if wikiRepo != nil {
wikiRepo.Close()
}
ctx.ServerError("WikiFilenameToName", err)
return
}
pages = append(pages, PageMeta{
Name: wikiName,
SubURL: models.WikiNameToSubURL(wikiName),
UpdatedUnix: util.TimeStamp(c.Author.When.Unix()),
})
}
ctx.Data["Pages"] = pages
defer func() {
if wikiRepo != nil {
wikiRepo.Close()
}
}()
ctx.HTML(200, tplWikiPages)
}
// WikiRaw outputs raw blob requested by user (image for example)
func WikiRaw(ctx *context.Context) {
wikiRepo, commit, err := findWikiRepoCommit(ctx)
if err != nil {
if wikiRepo != nil {
return
}
}
defer func() {
wikiRepo.Close()
}()
providedPath := ctx.Params("*")
var entry *git.TreeEntry
if commit != nil {
// Try to find a file with that name
entry, err = findEntryForFile(commit, providedPath)
if err != nil {
ctx.ServerError("findFile", err)
return
}
if entry == nil {
// Try to find a wiki page with that name
if strings.HasSuffix(providedPath, ".md") {
providedPath = providedPath[:len(providedPath)-3]
}
wikiPath := models.WikiNameToFilename(providedPath)
entry, err = findEntryForFile(commit, wikiPath)
if err != nil {
ctx.ServerError("findFile", err)
return
}
}
}
if entry != nil {
if err = ServeBlob(ctx, entry.Blob()); err != nil {
ctx.ServerError("ServeBlob", err)
}
return
}
ctx.NotFound("findEntryForFile", nil)
}
// NewWiki render wiki create page
func NewWiki(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
ctx.Data["PageIsWiki"] = true
ctx.Data["RequireSimpleMDE"] = true
if !ctx.Repo.Repository.HasWiki() {
ctx.Data["title"] = "Home"
}
ctx.HTML(200, tplWikiNew)
}
// NewWikiPost response for wiki create request
func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
ctx.Data["PageIsWiki"] = true
ctx.Data["RequireSimpleMDE"] = true
if ctx.HasError() {
ctx.HTML(200, tplWikiNew)
return
}
if util.IsEmptyString(form.Title) {
ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplWikiNew, form)
return
}
wikiName := models.NormalizeWikiName(form.Title)
if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil {
if models.IsErrWikiReservedName(err) {
ctx.Data["Err_Title"] = true
ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
} else if models.IsErrWikiAlreadyExist(err) {
ctx.Data["Err_Title"] = true
ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
} else {
ctx.ServerError("AddWikiPage", err)
}
return
}
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName))
}
// EditWiki render wiki modify page
func EditWiki(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
ctx.Data["PageIsWikiEdit"] = true
ctx.Data["RequireSimpleMDE"] = true
if !ctx.Repo.Repository.HasWiki() {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
return
}
renderWikiPage(ctx, false)
if ctx.Written() {
return
}
ctx.HTML(200, tplWikiNew)
}
// EditWikiPost response for wiki modify request
func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
ctx.Data["PageIsWiki"] = true
ctx.Data["RequireSimpleMDE"] = true
if ctx.HasError() {
ctx.HTML(200, tplWikiNew)
return
}
oldWikiName := models.NormalizeWikiName(ctx.Params(":page"))
newWikiName := models.NormalizeWikiName(form.Title)
if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
ctx.ServerError("EditWikiPage", err)
return
}
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName))
}
// DeleteWikiPagePost delete wiki page
func DeleteWikiPagePost(ctx *context.Context) {
wikiName := models.NormalizeWikiName(ctx.Params(":page"))
if len(wikiName) == 0 {
wikiName = "Home"
}
if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil {
ctx.ServerError("DeleteWikiPage", err)
return
}
ctx.JSON(200, map[string]interface{}{
"redirect": ctx.Repo.RepoLink + "/wiki/",
})
}