309 lines
8.4 KiB
Go
309 lines
8.4 KiB
Go
/*
|
|
* @Date: 2021-08-06 09:53:43
|
|
* @LastEditors: viletyy
|
|
* @LastEditTime: 2021-08-19 17:58:21
|
|
* @FilePath: /gitea-1120-rc1/modules/wikies/wiki.go
|
|
*/
|
|
package wikies
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"code.gitea.io/gitea/models"
|
|
repo_module "code.gitea.io/gitea/modules/repository"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/sync"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"github.com/unknwon/com"
|
|
|
|
//"github.com/unknwon/com"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"strings"
|
|
|
|
//"code.gitea.io/gitea/modules/wikies"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/log"
|
|
wiki_service "code.gitea.io/gitea/services/wiki"
|
|
)
|
|
|
|
var (
|
|
reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
|
|
wikiWorkingPool = sync.NewExclusivePool()
|
|
)
|
|
|
|
func nameAllowed(name string) error {
|
|
if util.IsStringInSlice(name, reservedWikiNames) {
|
|
return models.ErrWikiReservedName{
|
|
Title: name,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func InitWiki(repo *models.Repository) error {
|
|
if repo.HasWiki() {
|
|
return nil
|
|
}
|
|
|
|
if err := git.InitRepository(repo.WikiPath(), true); err != nil {
|
|
return fmt.Errorf("InitRepository: %v", err)
|
|
} else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
|
|
return fmt.Errorf("createDelegateHooks: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func FindWikiRepoCommit(ctx *context.APIContext) (*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
|
|
}
|
|
|
|
func WikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
|
|
pageFilename := wiki_service.NameToFilename(wikiName)
|
|
entry, err := findEntryForFile(commit, pageFilename)
|
|
if err != nil && !git.IsErrNotExist(err) {
|
|
ctx.ServerError("findEntryForFile", err)
|
|
return nil, nil, "", false
|
|
} else if entry == nil {
|
|
return nil, nil, "", true
|
|
}
|
|
return wikiContentsByEntry(ctx, entry), entry, pageFilename, false
|
|
}
|
|
|
|
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
|
|
entry, err := commit.GetTreeEntryByPath(target)
|
|
if err != nil && !git.IsErrNotExist(err) {
|
|
return nil, err
|
|
}
|
|
if entry != nil {
|
|
return entry, nil
|
|
}
|
|
|
|
// Then the unescaped, shortest alternative
|
|
var unescapedTarget string
|
|
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
|
|
return nil, err
|
|
}
|
|
return commit.GetTreeEntryByPath(unescapedTarget)
|
|
}
|
|
|
|
func wikiContentsByEntry(ctx *context.APIContext, 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
|
|
}
|
|
|
|
func CreateWikiPage(doer *models.User, repo *models.Repository, wikiName, content, message string) error {
|
|
return updateWikiPage(doer, repo, "", wikiName, content, message, true)
|
|
}
|
|
|
|
func EditWikiPage(doer *models.User, repo *models.Repository, oldWikiName, newWikiName, content, message string) error {
|
|
return updateWikiPage(doer, repo, oldWikiName, newWikiName, content, message, false)
|
|
}
|
|
func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, newWikiName, content, message string, isNew bool) (err error) {
|
|
if err = nameAllowed(newWikiName); err != nil {
|
|
return err
|
|
}
|
|
wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
|
|
defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
|
|
|
|
if err = InitWiki(repo); err != nil {
|
|
return fmt.Errorf("InitWiki: %v", err)
|
|
}
|
|
|
|
hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master")
|
|
|
|
basePath, err := models.CreateTemporaryPath("update-wiki")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err := models.RemoveTemporaryPath(basePath); err != nil {
|
|
log.Error("Merge: RemoveTemporaryPath: %s", err)
|
|
}
|
|
}()
|
|
|
|
cloneOpts := git.CloneRepoOptions{
|
|
Bare: true,
|
|
Shared: true,
|
|
}
|
|
|
|
if hasMasterBranch {
|
|
cloneOpts.Branch = "master"
|
|
}
|
|
|
|
if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil {
|
|
log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
|
|
return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
|
|
}
|
|
|
|
gitRepo, err := git.OpenRepository(basePath)
|
|
if err != nil {
|
|
log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
|
|
return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
if hasMasterBranch {
|
|
if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
|
|
log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)
|
|
return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
|
|
}
|
|
}
|
|
|
|
newWikiPath := wiki_service.NameToFilename(newWikiName)
|
|
if isNew {
|
|
filesInIndex, err := gitRepo.LsFiles(newWikiPath)
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
if util.IsStringInSlice(newWikiPath, filesInIndex) {
|
|
return models.ErrWikiAlreadyExist{
|
|
Title: newWikiPath,
|
|
}
|
|
}
|
|
} else {
|
|
oldWikiPath := wiki_service.NameToFilename(oldWikiName)
|
|
filesInIndex, err := gitRepo.LsFiles(oldWikiPath)
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
|
|
if util.IsStringInSlice(oldWikiPath, filesInIndex) {
|
|
err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
|
|
|
|
objectHash, err := gitRepo.HashObject(strings.NewReader(content))
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
|
|
if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
|
|
tree, err := gitRepo.WriteTree()
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
|
|
commitTreeOpts := git.CommitTreeOpts{
|
|
Message: message,
|
|
}
|
|
|
|
sign, signingKey, _ := repo.SignWikiCommit(doer)
|
|
if sign {
|
|
commitTreeOpts.KeyID = signingKey
|
|
} else {
|
|
commitTreeOpts.NoGPGSign = true
|
|
}
|
|
if hasMasterBranch {
|
|
commitTreeOpts.Parents = []string{"HEAD"}
|
|
}
|
|
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
|
|
if err != nil {
|
|
log.Error("%v", err)
|
|
return err
|
|
}
|
|
|
|
if err := git.Push(basePath, git.PushOptions{
|
|
Remote: "origin",
|
|
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
|
|
Env: models.FullPushingEnvironment(
|
|
doer,
|
|
doer,
|
|
repo,
|
|
repo.Name+".wiki",
|
|
0,
|
|
),
|
|
}); err != nil {
|
|
log.Error("%v", err)
|
|
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
|
|
return err
|
|
}
|
|
return fmt.Errorf("Push: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewWikiPost response for wiki create request
|
|
func NewWikiPost(ctx *context.APIContext, form api.WikiOption) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
|
|
ctx.Data["PageIsWiki"] = true
|
|
ctx.Data["RequireSimpleMDE"] = true
|
|
if util.IsEmptyString(form.Name) {
|
|
//ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), nil, form)
|
|
return
|
|
}
|
|
|
|
wikiName := wiki_service.NormalizeWikiName(form.Name)
|
|
if len(form.CommitMessage) == 0 {
|
|
form.CommitMessage = ctx.Tr("repo.editor.add", form.Name)
|
|
}
|
|
if err := wiki_service.AddWikiPage(ctx.User, ctx.Repo.Repository, wikiName, form.Content, form.CommitMessage); 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/" + wiki_service.NameToSubURL(wikiName))
|
|
}
|
|
func EditWikiPost(ctx *context.APIContext, form api.WikiOption) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
|
|
ctx.Data["PageIsWiki"] = true
|
|
ctx.Data["RequireSimpleMDE"] = true
|
|
|
|
oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
|
|
newWikiName := wiki_service.NormalizeWikiName(form.Name)
|
|
|
|
//newWikiName, _= url.QueryUnescape(newWikiName)
|
|
|
|
if len(form.CommitMessage) == 0 {
|
|
form.CommitMessage = ctx.Tr("repo.editor.update", form.Name)
|
|
}
|
|
|
|
if err := wiki_service.EditWikiPage(ctx.User, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.CommitMessage); err != nil {
|
|
ctx.ServerError("EditWikiPage", err)
|
|
return
|
|
}
|
|
}
|