mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-30 01:04:25 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			523 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			523 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 The Gogs Authors. All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package repo
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/url"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"code.gitea.io/git"
 | |
| 
 | |
| 	"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/markdown"
 | |
| 	"code.gitea.io/gitea/modules/markup"
 | |
| )
 | |
| 
 | |
| 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.Repository.EnableUnit(models.UnitTypeWiki) &&
 | |
| 		!ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalWiki) {
 | |
| 		ctx.Handle(404, "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
 | |
| 	URL     string
 | |
| 	Updated time.Time
 | |
| }
 | |
| 
 | |
| func urlEncoded(str string) string {
 | |
| 	u, err := url.Parse(str)
 | |
| 	if err != nil {
 | |
| 		return str
 | |
| 	}
 | |
| 	return u.String()
 | |
| }
 | |
| func urlDecoded(str string) string {
 | |
| 	res, err := url.QueryUnescape(str)
 | |
| 	if err != nil {
 | |
| 		return str
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| // commitTreeBlobEntry processes found file and checks if it matches search target
 | |
| func commitTreeBlobEntry(entry *git.TreeEntry, path string, targets []string, textOnly bool) *git.TreeEntry {
 | |
| 	name := entry.Name()
 | |
| 	ext := filepath.Ext(name)
 | |
| 	if !textOnly || markdown.IsMarkdownFile(name) || ext == ".textile" {
 | |
| 		for _, target := range targets {
 | |
| 			if matchName(path, target) || matchName(urlEncoded(path), target) || matchName(urlDecoded(path), target) {
 | |
| 				return entry
 | |
| 			}
 | |
| 			pathNoExt := strings.TrimSuffix(path, ext)
 | |
| 			if matchName(pathNoExt, target) || matchName(urlEncoded(pathNoExt), target) || matchName(urlDecoded(pathNoExt), target) {
 | |
| 				return entry
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // commitTreeDirEntry is a recursive file tree traversal function
 | |
| func commitTreeDirEntry(repo *git.Repository, commit *git.Commit, entries []*git.TreeEntry, prevPath string, targets []string, textOnly bool) (*git.TreeEntry, error) {
 | |
| 	for i := range entries {
 | |
| 		entry := entries[i]
 | |
| 		var path string
 | |
| 		if len(prevPath) == 0 {
 | |
| 			path = entry.Name()
 | |
| 		} else {
 | |
| 			path = prevPath + "/" + entry.Name()
 | |
| 		}
 | |
| 		if entry.Type == git.ObjectBlob {
 | |
| 			// File
 | |
| 			if res := commitTreeBlobEntry(entry, path, targets, textOnly); res != nil {
 | |
| 				return res, nil
 | |
| 			}
 | |
| 		} else if entry.IsDir() {
 | |
| 			// Directory
 | |
| 			// Get our tree entry, handling all possible errors
 | |
| 			var err error
 | |
| 			var tree *git.Tree
 | |
| 			if tree, err = repo.GetTree(entry.ID.String()); tree == nil || err != nil {
 | |
| 				if err == nil {
 | |
| 					err = fmt.Errorf("repo.GetTree(%s) => nil", entry.ID.String())
 | |
| 				}
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			// Found us, get children entries
 | |
| 			var ls git.Entries
 | |
| 			if ls, err = tree.ListEntries(); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			// Call itself recursively to find needed entry
 | |
| 			var te *git.TreeEntry
 | |
| 			if te, err = commitTreeDirEntry(repo, commit, ls, path, targets, textOnly); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			if te != nil {
 | |
| 				return te, nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, nil
 | |
| }
 | |
| 
 | |
| // commitTreeEntry is a first step of commitTreeDirEntry, which should be never called directly
 | |
| func commitTreeEntry(repo *git.Repository, commit *git.Commit, targets []string, textOnly bool) (*git.TreeEntry, error) {
 | |
| 	entries, err := commit.ListEntries()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return commitTreeDirEntry(repo, commit, entries, "", targets, textOnly)
 | |
| }
 | |
| 
 | |
| // findFile finds the best match for given filename in repo file tree
 | |
| func findFile(repo *git.Repository, commit *git.Commit, target string, textOnly bool) (*git.TreeEntry, error) {
 | |
| 	targets := []string{target, urlEncoded(target), urlDecoded(target)}
 | |
| 	var entry *git.TreeEntry
 | |
| 	var err error
 | |
| 	if entry, err = commitTreeEntry(repo, commit, targets, textOnly); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return entry, nil
 | |
| }
 | |
| 
 | |
| // matchName matches generic name representation of the file with required one
 | |
| func matchName(target, name string) bool {
 | |
| 	if len(target) != len(name) {
 | |
| 		return false
 | |
| 	}
 | |
| 	name = strings.ToLower(name)
 | |
| 	target = strings.ToLower(target)
 | |
| 	if name == target {
 | |
| 		return true
 | |
| 	}
 | |
| 	target = strings.Replace(target, " ", "?", -1)
 | |
| 	target = strings.Replace(target, "-", "?", -1)
 | |
| 	for i := range name {
 | |
| 		ch := name[i]
 | |
| 		reqCh := target[i]
 | |
| 		if ch != reqCh {
 | |
| 			if string(reqCh) != "?" {
 | |
| 				return false
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
 | |
| 	wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
 | |
| 	if err != nil {
 | |
| 		// ctx.Handle(500, "OpenRepository", err)
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	if !wikiRepo.IsBranchExist("master") {
 | |
| 		return wikiRepo, nil, nil
 | |
| 	}
 | |
| 
 | |
| 	commit, err := wikiRepo.GetBranchCommit("master")
 | |
| 	if err != nil {
 | |
| 		ctx.Handle(500, "GetBranchCommit", err)
 | |
| 		return wikiRepo, nil, err
 | |
| 	}
 | |
| 	return wikiRepo, commit, nil
 | |
| }
 | |
| 
 | |
| func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *git.TreeEntry) {
 | |
| 	wikiRepo, commit, err := findWikiRepoCommit(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	if commit == nil {
 | |
| 		return wikiRepo, nil
 | |
| 	}
 | |
| 
 | |
| 	// Get page list.
 | |
| 	if isViewPage {
 | |
| 		entries, err := commit.ListEntries()
 | |
| 		if err != nil {
 | |
| 			ctx.Handle(500, "ListEntries", err)
 | |
| 			return nil, nil
 | |
| 		}
 | |
| 		pages := []PageMeta{}
 | |
| 		for i := range entries {
 | |
| 			if entries[i].Type == git.ObjectBlob {
 | |
| 				name := entries[i].Name()
 | |
| 				ext := filepath.Ext(name)
 | |
| 				if markdown.IsMarkdownFile(name) || ext == ".textile" {
 | |
| 					name = strings.TrimSuffix(name, ext)
 | |
| 					if name == "" || name == "_Sidebar" || name == "_Footer" || name == "_Header" {
 | |
| 						continue
 | |
| 					}
 | |
| 					pages = append(pages, PageMeta{
 | |
| 						Name: models.ToWikiPageName(name),
 | |
| 						URL:  name,
 | |
| 					})
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		ctx.Data["Pages"] = pages
 | |
| 	}
 | |
| 
 | |
| 	pageURL := ctx.Params(":page")
 | |
| 	if len(pageURL) == 0 {
 | |
| 		pageURL = "Home"
 | |
| 	}
 | |
| 	ctx.Data["PageURL"] = pageURL
 | |
| 
 | |
| 	pageName := models.ToWikiPageName(pageURL)
 | |
| 	ctx.Data["old_title"] = pageName
 | |
| 	ctx.Data["Title"] = pageName
 | |
| 	ctx.Data["title"] = pageName
 | |
| 	ctx.Data["RequireHighlightJS"] = true
 | |
| 
 | |
| 	var entry *git.TreeEntry
 | |
| 	if entry, err = findFile(wikiRepo, commit, pageName, true); err != nil {
 | |
| 		ctx.Handle(500, "findFile", err)
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	if entry == nil {
 | |
| 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	blob := entry.Blob()
 | |
| 	r, err := blob.Data()
 | |
| 	if err != nil {
 | |
| 		ctx.Handle(500, "Data", err)
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	data, err := ioutil.ReadAll(r)
 | |
| 	if err != nil {
 | |
| 		ctx.Handle(500, "ReadAll", err)
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	sidebarPresent := false
 | |
| 	sidebarContent := []byte{}
 | |
| 	sentry, err := findFile(wikiRepo, commit, "_Sidebar", true)
 | |
| 	if err == nil && sentry != nil {
 | |
| 		r, err = sentry.Blob().Data()
 | |
| 		if err == nil {
 | |
| 			dataSB, err := ioutil.ReadAll(r)
 | |
| 			if err == nil {
 | |
| 				sidebarPresent = true
 | |
| 				sidebarContent = dataSB
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	footerPresent := false
 | |
| 	footerContent := []byte{}
 | |
| 	sentry, err = findFile(wikiRepo, commit, "_Footer", true)
 | |
| 	if err == nil && sentry != nil {
 | |
| 		r, err = sentry.Blob().Data()
 | |
| 		if err == nil {
 | |
| 			dataSB, err := ioutil.ReadAll(r)
 | |
| 			if err == nil {
 | |
| 				footerPresent = true
 | |
| 				footerContent = dataSB
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if isViewPage {
 | |
| 		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
 | |
| 
 | |
| 	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() {
 | |
| 		return
 | |
| 	}
 | |
| 	if entry == nil {
 | |
| 		ctx.Data["Title"] = ctx.Tr("repo.wiki")
 | |
| 		ctx.HTML(200, tplWikiStart)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ename := entry.Name()
 | |
| 	if markup.Type(ename) != markdown.MarkupName {
 | |
| 		ext := strings.ToUpper(filepath.Ext(ename))
 | |
| 		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(ename)
 | |
| 	if err != nil {
 | |
| 		ctx.Handle(500, "GetCommitByPath", err)
 | |
| 		return
 | |
| 	}
 | |
| 	ctx.Data["Author"] = lastCommit.Author
 | |
| 
 | |
| 	ctx.HTML(200, tplWikiView)
 | |
| }
 | |
| 
 | |
| // WikiPages render wiki pages list page
 | |
| func WikiPages(ctx *context.Context) {
 | |
| 	ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
 | |
| 	ctx.Data["PageIsWiki"] = true
 | |
| 
 | |
| 	if !ctx.Repo.Repository.HasWiki() {
 | |
| 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	wikiRepo, commit, err := findWikiRepoCommit(ctx)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	entries, err := commit.ListEntries()
 | |
| 	if err != nil {
 | |
| 		ctx.Handle(500, "ListEntries", err)
 | |
| 		return
 | |
| 	}
 | |
| 	pages := make([]PageMeta, 0, len(entries))
 | |
| 	for i := range entries {
 | |
| 		if entries[i].Type == git.ObjectBlob {
 | |
| 			c, err := wikiRepo.GetCommitByPath(entries[i].Name())
 | |
| 			if err != nil {
 | |
| 				ctx.Handle(500, "GetCommit", err)
 | |
| 				return
 | |
| 			}
 | |
| 			name := entries[i].Name()
 | |
| 			ext := filepath.Ext(name)
 | |
| 			if markdown.IsMarkdownFile(name) || ext == ".textile" {
 | |
| 				name = strings.TrimSuffix(name, ext)
 | |
| 				if name == "" {
 | |
| 					continue
 | |
| 				}
 | |
| 				pages = append(pages, PageMeta{
 | |
| 					Name:    models.ToWikiPageName(name),
 | |
| 					URL:     name,
 | |
| 					Updated: c.Author.When,
 | |
| 				})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	ctx.Data["Pages"] = pages
 | |
| 
 | |
| 	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
 | |
| 		}
 | |
| 	}
 | |
| 	uri := ctx.Params("*")
 | |
| 	var entry *git.TreeEntry
 | |
| 	if commit != nil {
 | |
| 		entry, err = findFile(wikiRepo, commit, uri, false)
 | |
| 	}
 | |
| 	if err != nil || entry == nil {
 | |
| 		if entry == nil || commit == nil {
 | |
| 			defBranch := ctx.Repo.Repository.DefaultBranch
 | |
| 			if commit, err = ctx.Repo.GitRepo.GetBranchCommit(defBranch); commit == nil || err != nil {
 | |
| 				ctx.Handle(500, "GetBranchCommit", err)
 | |
| 				return
 | |
| 			}
 | |
| 			if entry, err = findFile(ctx.Repo.GitRepo, commit, uri, false); err != nil {
 | |
| 				ctx.Handle(500, "findFile", err)
 | |
| 				return
 | |
| 			}
 | |
| 			if entry == nil {
 | |
| 				ctx.Handle(404, "findFile", nil)
 | |
| 				return
 | |
| 			}
 | |
| 		} else {
 | |
| 			ctx.Handle(500, "findFile", err)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	if err = ServeBlob(ctx, entry.Blob()); err != nil {
 | |
| 		ctx.Handle(500, "ServeBlob", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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 fro 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
 | |
| 	}
 | |
| 
 | |
| 	wikiPath := models.ToWikiPageURL(form.Title)
 | |
| 
 | |
| 	if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiPath, form.Content, form.Message); err != nil {
 | |
| 		if models.IsErrWikiAlreadyExist(err) {
 | |
| 			ctx.Data["Err_Title"] = true
 | |
| 			ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
 | |
| 		} else {
 | |
| 			ctx.Handle(500, "AddWikiPage", err)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wikiPath)
 | |
| }
 | |
| 
 | |
| // 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 fro 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
 | |
| 	}
 | |
| 
 | |
| 	oldWikiPath := models.ToWikiPageURL(ctx.Params(":page"))
 | |
| 	newWikiPath := models.ToWikiPageURL(form.Title)
 | |
| 
 | |
| 	if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiPath, newWikiPath, form.Content, form.Message); err != nil {
 | |
| 		ctx.Handle(500, "EditWikiPage", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + newWikiPath)
 | |
| }
 | |
| 
 | |
| // DeleteWikiPagePost delete wiki page
 | |
| func DeleteWikiPagePost(ctx *context.Context) {
 | |
| 	pageURL := models.ToWikiPageURL(ctx.Params(":page"))
 | |
| 	if len(pageURL) == 0 {
 | |
| 		pageURL = "Home"
 | |
| 	}
 | |
| 
 | |
| 	if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, pageURL); err != nil {
 | |
| 		ctx.Handle(500, "DeleteWikiPage", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx.JSON(200, map[string]interface{}{
 | |
| 		"redirect": ctx.Repo.RepoLink + "/wiki/",
 | |
| 	})
 | |
| }
 |