Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
f2d7046f8f |
|
|
@ -0,0 +1,22 @@
|
|||
package structs
|
||||
|
||||
// CreateWikiDirOptions form for creating wiki dir
|
||||
type CreateWikiDirOptions struct {
|
||||
// dir name. leave empty to keep unchanged
|
||||
DirName string `json:"dir_name"`
|
||||
// optional commit message summarizing the change
|
||||
Message string `json:"message"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// CreateWikiPageOptions form for creating wiki
|
||||
type CreateWikiPageOptions struct {
|
||||
// page title. leave empty to keep unchanged
|
||||
Title string `json:"title"`
|
||||
// content must be base64 encoded
|
||||
ContentBase64 string `json:"content_base64"`
|
||||
// optional commit message summarizing the change
|
||||
Message string `json:"message"`
|
||||
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
|
@ -79,15 +79,26 @@ func Routers(ctx gocontext.Context) *web.Route {
|
|||
m.Group("/tags", func() {
|
||||
m.Get("", repo.ListTags)
|
||||
}, reqRepoReader(unit_model.TypeCode), context.ReferencesGitRepo(true))
|
||||
m.Group("/wikies", func() {
|
||||
m.Combo("").Get(repo.ListWikiPages).
|
||||
Post(bind(hat_api.WikiOption{}), repo.CreateWiki)
|
||||
m.Group("/{page}", func() {
|
||||
m.Combo("").Get(repo.GetWiki).
|
||||
Patch(bind(hat_api.WikiOption{}), repo.EditWiki).
|
||||
Delete(repo.DeleteWiki)
|
||||
})
|
||||
})
|
||||
//m.Group("/wikies", func() {
|
||||
// m.Combo("").
|
||||
// Get(repo.ListWikiPages).
|
||||
// Post(bind(hat_api.WikiOption{}), repo.CreateWiki)
|
||||
// m.Group("/{page}", func() {
|
||||
// m.Combo("").Get(repo.GetWiki).
|
||||
// Patch(bind(hat_api.WikiOption{}), repo.EditWiki).
|
||||
// Delete(repo.DeleteWiki)
|
||||
// })
|
||||
//})
|
||||
m.Group("/wiki", func() {
|
||||
//m.Combo("/page/{pageName}").
|
||||
// Get(repo.GetWikiPage).
|
||||
// Patch(mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
|
||||
// Delete(mustNotBeArchived, reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
|
||||
//m.Get("/revisions/{pageName}", repo.ListPageRevisions)
|
||||
m.Post("/new", mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(hat_api.CreateWikiPageOptions{}), repo.NewWikiPage)
|
||||
m.Post("/new/dir", mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(hat_api.CreateWikiDirOptions{}), repo.NewWikiDir)
|
||||
//m.Get("/pages", repo.ListWikiPages)
|
||||
}, mustEnableWiki)
|
||||
m.Group("/readme", func() {
|
||||
m.Get("", repo.GetReadmeContents)
|
||||
m.Get("/*", repo.GetReadmeContentsByPath)
|
||||
|
|
@ -179,6 +190,20 @@ func Routers(ctx gocontext.Context) *web.Route {
|
|||
return m
|
||||
}
|
||||
|
||||
func mustNotBeArchived(ctx *context.APIContext) {
|
||||
if ctx.Repo.Repository.IsArchived {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func mustEnableWiki(ctx *context.APIContext) {
|
||||
if !(ctx.Repo.CanRead(unit.TypeWiki)) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func securityHeaders() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,406 @@
|
|||
// Copyright 2021 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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/notification"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
wiki_service "code.gitea.io/gitea/services/wiki"
|
||||
hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git"
|
||||
hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
|
||||
wiki_service "code.gitlink.org.cn/Gitlink/gitea_hat.git/services/wiki"
|
||||
)
|
||||
|
||||
func ListWikiPages(ctx *context.APIContext) {
|
||||
if !ctx.Repo.Repository.HasWiki() {
|
||||
// NewWikiPage response for wiki create request
|
||||
func NewWikiPage(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/wiki/new repository repoCreateWikiPage
|
||||
// ---
|
||||
// summary: Create a wiki page
|
||||
// consumes:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateWikiPageOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/WikiPage"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
form := web.GetForm(ctx).(*hat_api.CreateWikiPageOptions)
|
||||
|
||||
if util.IsEmptyString(form.Title) {
|
||||
ctx.Error(http.StatusBadRequest, "emptyTitle", nil)
|
||||
return
|
||||
}
|
||||
|
||||
wikiCloneWiki := ctx.Repo.Repository.WikiCloneLink()
|
||||
wikiRepo, commit, err := findWikiRepoCommit(ctx.Context)
|
||||
defer func() {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
wikiName := wiki_service.NormalizeWikiName(form.Title)
|
||||
|
||||
if len(form.Message) == 0 {
|
||||
form.Message = fmt.Sprintf("Add '%s'", form.Title)
|
||||
}
|
||||
|
||||
if len(form.Path) > 0 {
|
||||
path := form.Path
|
||||
if path[len(path)-1] != '/' {
|
||||
form.Path = form.Path + "/"
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.ServerError("findWikiRepoCommit", err)
|
||||
ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
|
||||
return
|
||||
}
|
||||
form.ContentBase64 = string(content)
|
||||
|
||||
if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Path, form.ContentBase64, form.Message); err != nil {
|
||||
if repo_model.IsErrWikiReservedName(err) {
|
||||
ctx.Error(http.StatusBadRequest, "IsErrWikiReservedName", err)
|
||||
} else if repo_model.IsErrWikiAlreadyExist(err) {
|
||||
ctx.Error(http.StatusBadRequest, "IsErrWikiAlreadyExists", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "AddWikiPage", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
wikiPage := getWikiPage(ctx, wikiName, form.Path)
|
||||
|
||||
if !ctx.Written() {
|
||||
notification.NotifyNewWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName, form.Message)
|
||||
ctx.JSON(http.StatusCreated, wikiPage)
|
||||
}
|
||||
}
|
||||
func NewWikiDir(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*hat_api.CreateWikiDirOptions)
|
||||
|
||||
if util.IsEmptyString(form.DirName) {
|
||||
ctx.Error(http.StatusBadRequest, "emptyDirName", nil)
|
||||
return
|
||||
}
|
||||
|
||||
dirName := wiki_service.NormalizeWikiName(form.DirName)
|
||||
|
||||
if len(form.Message) == 0 {
|
||||
form.Message = fmt.Sprintf("Add Dir '%s'", form.DirName)
|
||||
}
|
||||
|
||||
if len(form.Path) > 0 {
|
||||
path := form.Path
|
||||
if path[len(path)-1] != '/' {
|
||||
form.Path = form.Path + "/"
|
||||
}
|
||||
}
|
||||
|
||||
if err := wiki_service.AddWikiDir(ctx, ctx.Doer, ctx.Repo.Repository, dirName, form.Path, form.Message); err != nil {
|
||||
if repo_model.IsErrWikiReservedName(err) {
|
||||
ctx.Error(http.StatusBadRequest, "IsErrWikiDirReservedName", err)
|
||||
} else if repo_model.IsErrWikiAlreadyExist(err) {
|
||||
ctx.Error(http.StatusBadRequest, "IsErrWikiDirAlreadyExists", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "AddWikiDir", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
wikiPage := getWikiDir(ctx, dirName, form.Path)
|
||||
|
||||
if !ctx.Written() {
|
||||
notification.NotifyNewWikiPage(ctx.Doer, ctx.Repo.Repository, dirName, form.Message)
|
||||
ctx.JSON(http.StatusCreated, wikiPage)
|
||||
}
|
||||
}
|
||||
|
||||
// EditWikiPage response for wiki modify request
|
||||
func EditWikiPage(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/wiki/page/{pageName} repository repoEditWikiPage
|
||||
// ---
|
||||
// summary: Edit a wiki page
|
||||
// consumes:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: pageName
|
||||
// in: path
|
||||
// description: name of the page
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateWikiPageOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WikiPage"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
form := web.GetForm(ctx).(*hat_api.CreateWikiPageOptions)
|
||||
|
||||
oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
|
||||
newWikiName := wiki_service.NormalizeWikiName(form.Title)
|
||||
|
||||
if len(newWikiName) == 0 {
|
||||
newWikiName = oldWikiName
|
||||
}
|
||||
|
||||
if len(form.Message) == 0 {
|
||||
form.Message = fmt.Sprintf("Update '%s'", newWikiName)
|
||||
}
|
||||
if len(form.Path) > 0 {
|
||||
path := form.Path
|
||||
if path[len(path)-1] != '/' {
|
||||
form.Path = form.Path + "/"
|
||||
}
|
||||
}
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.ContentBase64)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusBadRequest, "invalid base64 encoding of content", err)
|
||||
return
|
||||
}
|
||||
form.ContentBase64 = string(content)
|
||||
|
||||
if err := wiki_service.EditWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.ContentBase64, form.Message); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "EditWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
wikiPage := getWikiPage(ctx, newWikiName, form.Path)
|
||||
|
||||
if !ctx.Written() {
|
||||
notification.NotifyEditWikiPage(ctx.Doer, ctx.Repo.Repository, newWikiName, form.Message)
|
||||
ctx.JSON(http.StatusOK, wikiPage)
|
||||
}
|
||||
}
|
||||
|
||||
func getWikiPage(ctx *context.APIContext, title, path string) *api.WikiPage {
|
||||
title = wiki_service.NormalizeWikiName(title)
|
||||
|
||||
wikiRepo, commit := findWikiRepoCommit(ctx)
|
||||
if wikiRepo != nil {
|
||||
defer wikiRepo.Close()
|
||||
}
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookup filename in wiki - get filecontent, real filename
|
||||
content, pageFilename := wikiContentsByName(ctx, commit, title, path, false)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
sidebarContent, _ := wikiContentsByName(ctx, commit, "_Sidebar", path, true)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
footerContent, _ := wikiContentsByName(ctx, commit, "_Footer", path, true)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
||||
|
||||
// Get last change information.
|
||||
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &api.WikiPage{
|
||||
WikiPageMetaData: convert.ToWikiPageMetaData(title, lastCommit, ctx.Repo.Repository),
|
||||
ContentBase64: content,
|
||||
CommitCount: commitsCount,
|
||||
Sidebar: sidebarContent,
|
||||
Footer: footerContent,
|
||||
}
|
||||
}
|
||||
func getWikiDir(ctx *context.APIContext, dirName, path string) *api.WikiPage {
|
||||
dirName = wiki_service.NormalizeWikiName(dirName)
|
||||
|
||||
wikiRepo, commit := findWikiRepoCommit(ctx)
|
||||
if wikiRepo != nil {
|
||||
defer wikiRepo.Close()
|
||||
}
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookup filename in wiki - get filecontent, real filename
|
||||
content, pageFilename := wikiDirByName(ctx, commit, dirName, path, false)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
||||
|
||||
// Get last change information.
|
||||
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommitByPath", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &api.WikiPage{
|
||||
WikiPageMetaData: convert.ToWikiPageMetaData(dirName, lastCommit, ctx.Repo.Repository),
|
||||
ContentBase64: content,
|
||||
CommitCount: commitsCount,
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteWikiPage delete wiki page
|
||||
func DeleteWikiPage(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/wiki/page/{pageName} repository repoDeleteWikiPage
|
||||
// ---
|
||||
// summary: Delete a wiki page
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: pageName
|
||||
// in: path
|
||||
// description: name of the page
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
wikiName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
|
||||
|
||||
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
|
||||
if err.Error() == "file does not exist" {
|
||||
ctx.NotFound(err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
notification.NotifyDeleteWikiPage(ctx.Doer, ctx.Repo.Repository, wikiName)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListWikiPages get wiki pages list
|
||||
func ListWikiPages(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/wiki/pages repository repoGetWikiPages
|
||||
// ---
|
||||
// summary: Get all wiki pages
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WikiPageList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
wikiRepo, commit := findWikiRepoCommit(ctx)
|
||||
if wikiRepo != nil {
|
||||
defer wikiRepo.Close()
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := ctx.FormInt("limit")
|
||||
if limit <= 1 {
|
||||
limit = setting.API.DefaultPagingNum
|
||||
}
|
||||
|
||||
skip := (page - 1) * limit
|
||||
max := page * limit
|
||||
|
||||
entries, err := commit.ListEntries()
|
||||
if err != nil {
|
||||
ctx.ServerError("ListEntries", err)
|
||||
return
|
||||
}
|
||||
|
||||
pages := make([]hat_api.WikiesResponse, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsRegular() {
|
||||
pages := make([]*api.WikiPageMetaData, 0, len(entries))
|
||||
for i, entry := range entries {
|
||||
if i < skip || i >= max || !entry.IsRegular() {
|
||||
continue
|
||||
}
|
||||
lastCommit, firstCommit, err := hat_git.GetFirstAndLastCommitByPath(wikiRepo, "master", entry.Name())
|
||||
c, err := wikiRepo.GetCommitByPath(entry.Name())
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitByPath", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
wikiName, err := wiki_service.FilenameToName(entry.Name())
|
||||
|
|
@ -59,157 +408,128 @@ func ListWikiPages(ctx *context.APIContext) {
|
|||
if repo_model.IsErrWikiInvalidFileName(err) {
|
||||
continue
|
||||
}
|
||||
ctx.ServerError("wiki_service.FilenameToName", err)
|
||||
ctx.Error(http.StatusInternalServerError, "WikiFilenameToName", err)
|
||||
return
|
||||
}
|
||||
pages = append(pages, hat_api.WikiesResponse{
|
||||
WikiCloneLink: hat_api.CloneLink{
|
||||
HTTPS: wikiCloneWiki.HTTPS,
|
||||
SSH: wikiCloneWiki.SSH,
|
||||
},
|
||||
WikiMeta: hat_api.WikiMeta{
|
||||
Name: wikiName,
|
||||
Commit: hat_api.WikiCommit{
|
||||
Author: hat_api.WikiUser{
|
||||
Name: lastCommit.Author.Name,
|
||||
Email: lastCommit.Author.Email,
|
||||
When: lastCommit.Author.When.Unix(),
|
||||
},
|
||||
Commiter: hat_api.WikiUser{
|
||||
Name: lastCommit.Committer.Name,
|
||||
Email: lastCommit.Committer.Email,
|
||||
When: lastCommit.Author.When.Unix(),
|
||||
},
|
||||
ID: lastCommit.ID.String(),
|
||||
Message: lastCommit.Message(),
|
||||
},
|
||||
FirstCommit: hat_api.WikiCommit{
|
||||
Author: hat_api.WikiUser{
|
||||
Name: firstCommit.Author.Name,
|
||||
Email: firstCommit.Author.Email,
|
||||
When: firstCommit.Author.When.Unix(),
|
||||
},
|
||||
Commiter: hat_api.WikiUser{
|
||||
Name: firstCommit.Committer.Name,
|
||||
Email: firstCommit.Committer.Email,
|
||||
When: firstCommit.Author.When.Unix(),
|
||||
},
|
||||
ID: firstCommit.ID.String(),
|
||||
Message: firstCommit.Message(),
|
||||
},
|
||||
},
|
||||
})
|
||||
pages = append(pages, convert.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
|
||||
}
|
||||
|
||||
sort.Slice(pages, func(i, j int) bool {
|
||||
return pages[i].FirstCommit.Author.When > pages[j].FirstCommit.Author.When
|
||||
})
|
||||
|
||||
ctx.SetTotalCountHeader(int64(len(entries)))
|
||||
ctx.JSON(http.StatusOK, pages)
|
||||
}
|
||||
|
||||
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
|
||||
wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
// GetWikiPage get single wiki page
|
||||
func GetWikiPage(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/wiki/page/{pageName} repository repoGetWikiPage
|
||||
// ---
|
||||
// summary: Get a wiki page
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: pageName
|
||||
// in: path
|
||||
// description: name of the page
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WikiPage"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
commit, err := wikiRepo.GetBranchCommit("master")
|
||||
if err != nil {
|
||||
return wikiRepo, nil, err
|
||||
// get requested pagename
|
||||
pageName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
|
||||
//todo:fix path
|
||||
wikiPage := getWikiPage(ctx, pageName, "")
|
||||
if !ctx.Written() {
|
||||
ctx.JSON(http.StatusOK, wikiPage)
|
||||
}
|
||||
return wikiRepo, commit, nil
|
||||
}
|
||||
|
||||
func CreateWiki(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*hat_api.WikiOption)
|
||||
// ListPageRevisions renders file revision list of wiki page
|
||||
func ListPageRevisions(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/wiki/revisions/{pageName} repository repoGetWikiPageRevisions
|
||||
// ---
|
||||
// summary: Get revisions of a wiki page
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: pageName
|
||||
// in: path
|
||||
// description: name of the page
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WikiCommitList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// wikiName := wiki_service.NormalizeWikiName(form.Name)
|
||||
wikiName := form.Name
|
||||
wikiCloneLink := ctx.Repo.Repository.WikiCloneLink()
|
||||
|
||||
if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.CommitMessage); err != nil {
|
||||
if repo_model.IsErrWikiReservedName(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "WikiNameIsReservedPage", "wiki名称是被保留的.")
|
||||
} else if repo_model.IsErrWikiAlreadyExist(err) {
|
||||
ctx.Error(http.StatusConflict, "WikiNameAlreadyExist", "wiki名称已存在")
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "AddWikiPage", err.Error())
|
||||
}
|
||||
wikiRepo, commit := findWikiRepoCommit(ctx)
|
||||
if wikiRepo != nil {
|
||||
defer wikiRepo.Close()
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
wikiRepo, commit, _ := findWikiRepoCommit(ctx)
|
||||
data, entry, pageFilename := wikiContentsByName(ctx, commit, wikiName)
|
||||
metas := ctx.Repo.Repository.ComposeDocumentMetas()
|
||||
|
||||
var rctx = &markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: metas,
|
||||
IsWiki: true,
|
||||
// get requested pagename
|
||||
pageName := wiki_service.NormalizeWikiName(ctx.Params(":pageName"))
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
if err := markdown.Render(rctx, bytes.NewReader(data), &buf); err != nil {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
}
|
||||
ctx.ServerError("Render", err)
|
||||
//todo:fix path
|
||||
// lookup filename in wiki - get filecontent, gitTree entry , real filename
|
||||
_, pageFilename := wikiContentsByName(ctx, commit, pageName, "", false)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
||||
c, err := wikiRepo.GetCommitByPath(entry.Name())
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
// get Commit Count
|
||||
commitsHistory, err := wikiRepo.CommitsByFileAndRange("master", pageFilename, page)
|
||||
if err != nil {
|
||||
if repo_model.IsErrWikiInvalidFileName(err) {
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "CommitsByFileAndRange", err)
|
||||
return
|
||||
}
|
||||
wiki := hat_api.WikiResponse{
|
||||
WikiCloneLink: hat_api.CloneLink{
|
||||
HTTPS: wikiCloneLink.HTTPS,
|
||||
SSH: wikiCloneLink.SSH,
|
||||
},
|
||||
WikiMeta: hat_api.WikiMeta{
|
||||
Name: form.Name,
|
||||
Commit: hat_api.WikiCommit{
|
||||
Author: hat_api.WikiUser{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
When: c.Author.When.Unix(),
|
||||
},
|
||||
Commiter: hat_api.WikiUser{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
When: c.Author.When.Unix(),
|
||||
},
|
||||
ID: c.ID.String(),
|
||||
Message: c.Message(),
|
||||
},
|
||||
},
|
||||
CommitCounts: commitsCount,
|
||||
MdContent: string(data),
|
||||
SimpleContent: buf.String(),
|
||||
}
|
||||
ctx.JSON(http.StatusOK, wiki)
|
||||
}
|
||||
|
||||
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 := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
ctx.ServerError("ReadAll", err)
|
||||
return nil
|
||||
}
|
||||
return content
|
||||
|
||||
ctx.SetTotalCountHeader(commitsCount)
|
||||
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
|
||||
}
|
||||
|
||||
// findEntryForFile finds the tree entry for a target filepath.
|
||||
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
|
||||
entry, err := commit.GetTreeEntryByPath(target)
|
||||
if err != nil {
|
||||
|
|
@ -227,184 +547,76 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error)
|
|||
return commit.GetTreeEntryByPath(unescapedTarget)
|
||||
}
|
||||
|
||||
// findWikiRepoCommit opens the wiki repo and returns the latest commit, writing to context on error.
|
||||
// The caller is responsible for closing the returned repo again
|
||||
func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) {
|
||||
wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
|
||||
if err != nil {
|
||||
|
||||
if git.IsErrNotExist(err) || err.Error() == "no such file or directory" {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commit, err := wikiRepo.GetBranchCommit("master")
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
|
||||
}
|
||||
return wikiRepo, nil
|
||||
}
|
||||
return wikiRepo, commit
|
||||
}
|
||||
|
||||
// wikiContentsByEntry returns the contents of the wiki page referenced by the
|
||||
// given tree entry, encoded with base64. Writes to ctx if an error occurs.
|
||||
func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
|
||||
blob := entry.Blob()
|
||||
if blob.Size() > setting.API.DefaultMaxBlobSize {
|
||||
return ""
|
||||
}
|
||||
content, err := blob.GetBlobContentBase64()
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err)
|
||||
return ""
|
||||
}
|
||||
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, *git.TreeEntry, string) {
|
||||
pageFilename := wiki_service.NameToFilename(wikiName)
|
||||
func wikiContentsByName(ctx *context.APIContext, commit *git.Commit, wikiName, path string, isSidebarOrFooter bool) (string, string) {
|
||||
pageFilename := wiki_service.NameToFilename(wikiName, path)
|
||||
entry, err := findEntryForFile(commit, pageFilename)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
if !isSidebarOrFooter {
|
||||
ctx.NotFound()
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("findEntryForFile", err)
|
||||
}
|
||||
return nil, nil, ""
|
||||
return "", ""
|
||||
}
|
||||
return wikiContentsByEntry(ctx, entry), entry, pageFilename
|
||||
return wikiContentsByEntry(ctx, entry), pageFilename
|
||||
}
|
||||
|
||||
func GetWiki(ctx *context.APIContext) {
|
||||
wikiRepo, commit, _ := findWikiRepoCommit(ctx.Context)
|
||||
|
||||
defer func() {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
wikiCloneWiki := ctx.Repo.Repository.WikiCloneLink()
|
||||
|
||||
// pageName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
|
||||
pageName := ctx.Params(":page")
|
||||
if len(pageName) == 0 {
|
||||
pageName = "Home"
|
||||
}
|
||||
data, entry, pageFileName := wikiContentsByName(ctx.Context, commit, pageName)
|
||||
if entry == nil {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
metas := ctx.Repo.Repository.ComposeDocumentMetas()
|
||||
var rctx = &markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: metas,
|
||||
IsWiki: true,
|
||||
}
|
||||
var buf strings.Builder
|
||||
content := blackfriday.Run(data)
|
||||
if err := markdown.Render(rctx, bytes.NewReader(data), &buf); err != nil {
|
||||
ctx.ServerError("markdown.Render", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := wikiRepo.GetCommitByPath(entry.Name())
|
||||
func wikiDirByName(ctx *context.APIContext, commit *git.Commit, dirName, path string, isSidebarOrFooter bool) (string, string) {
|
||||
pageFilename := wiki_service.NameToDirName(dirName, path)
|
||||
entry, err := findEntryForFile(commit, pageFilename)
|
||||
if err != nil {
|
||||
if repo_model.IsErrWikiInvalidFileName(err) {
|
||||
return
|
||||
if git.IsErrNotExist(err) {
|
||||
if !isSidebarOrFooter {
|
||||
ctx.NotFound()
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("findEntryForFile", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFileName)
|
||||
wiki := hat_api.WikiResponse{
|
||||
WikiCloneLink: hat_api.CloneLink{
|
||||
HTTPS: wikiCloneWiki.HTTPS,
|
||||
SSH: wikiCloneWiki.SSH,
|
||||
},
|
||||
WikiMeta: hat_api.WikiMeta{
|
||||
Name: pageName,
|
||||
Commit: hat_api.WikiCommit{
|
||||
Author: hat_api.WikiUser{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
When: c.Author.When.Unix(),
|
||||
},
|
||||
Commiter: hat_api.WikiUser{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
When: c.Committer.When.Unix(),
|
||||
},
|
||||
},
|
||||
},
|
||||
CommitCounts: commitsCount,
|
||||
MdContent: string(data),
|
||||
SimpleContent: string(content),
|
||||
}
|
||||
ctx.JSON(http.StatusOK, wiki)
|
||||
}
|
||||
|
||||
func EditWiki(ctx *context.APIContext) {
|
||||
form := web.GetForm(ctx).(*hat_api.WikiOption)
|
||||
// oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
|
||||
oldWikiName := ctx.Params(":page")
|
||||
// newWikiName := wiki_service.NormalizeWikiName(form.Name)
|
||||
newWikiName := form.Name
|
||||
|
||||
wikiRepo, commit, _ := findWikiRepoCommit(ctx.Context)
|
||||
|
||||
defer func() {
|
||||
if wikiRepo != nil {
|
||||
wikiRepo.Close()
|
||||
}
|
||||
}()
|
||||
if _, entry, _ := wikiContentsByName(ctx.Context, commit, oldWikiName); entry == nil {
|
||||
ctx.Error(http.StatusNotFound, "WikiNotFound", "Wiki不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if _, entry, _ := wikiContentsByName(ctx.Context, commit, newWikiName); oldWikiName != newWikiName && entry != nil {
|
||||
ctx.Error(http.StatusNotFound, "WikiNameAlreadyExist", "Wiki已存在")
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.CommitMessage) == 0 {
|
||||
form.CommitMessage = ctx.Tr("repo.editor.update", form.Name)
|
||||
}
|
||||
|
||||
if err := wiki_service.EditWikiPage(ctx.Context, ctx.Doer, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.CommitMessage); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "EditWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, newCommit, _ := findWikiRepoCommit(ctx.Context)
|
||||
data, entry, pageFilename := wikiContentsByName(ctx.Context, newCommit, newWikiName)
|
||||
c, err := wikiRepo.GetCommitByPath(entry.Name())
|
||||
if err != nil {
|
||||
if repo_model.IsErrWikiInvalidFileName(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
metas := ctx.Repo.Repository.ComposeDocumentMetas()
|
||||
|
||||
var rctx = &markup.RenderContext{
|
||||
URLPrefix: ctx.Repo.RepoLink,
|
||||
Metas: metas,
|
||||
IsWiki: true,
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
if err := markdown.Render(rctx, bytes.NewReader(data), &buf); err != nil {
|
||||
ctx.ServerError("markdown.Render", err)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
||||
wiki := hat_api.WikiResponse{
|
||||
WikiMeta: hat_api.WikiMeta{
|
||||
Name: form.Name,
|
||||
Commit: hat_api.WikiCommit{
|
||||
Author: hat_api.WikiUser{
|
||||
Name: c.Author.Name,
|
||||
Email: c.Author.Email,
|
||||
When: c.Author.When.Unix(),
|
||||
},
|
||||
Commiter: hat_api.WikiUser{
|
||||
Name: c.Committer.Name,
|
||||
Email: c.Committer.Email,
|
||||
When: c.Author.When.Unix(),
|
||||
},
|
||||
ID: c.ID.String(),
|
||||
Message: c.Message(),
|
||||
},
|
||||
},
|
||||
CommitCounts: commitsCount,
|
||||
MdContent: string(data),
|
||||
SimpleContent: buf.String(),
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, wiki)
|
||||
}
|
||||
|
||||
func DeleteWiki(ctx *context.APIContext) {
|
||||
// wikiName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
|
||||
wikiName := ctx.Params(":page")
|
||||
if len(wikiName) == 0 {
|
||||
wikiName = "Home"
|
||||
}
|
||||
|
||||
err := wiki_service.DeleteWikiPage(ctx.Context, ctx.Doer, ctx.Repo.Repository, wikiName)
|
||||
if err != nil {
|
||||
ctx.ServerError("wiki_service.DeleteWikiPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return wikiContentsByEntry(ctx, entry), pageFilename
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,437 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 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 wiki
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"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/sync"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
)
|
||||
|
||||
var (
|
||||
reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
|
||||
// TODO: use clustered lock (unique queue? or *abuse* cache)
|
||||
wikiWorkingPool = sync.NewExclusivePool()
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultRemote = "origin"
|
||||
DefaultBranch = "master"
|
||||
)
|
||||
|
||||
func nameAllowed(name string) error {
|
||||
if util.IsStringInSlice(name, reservedWikiNames) {
|
||||
return repo_model.ErrWikiReservedName{
|
||||
Title: name,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NameToSubURL converts a wiki name to its corresponding sub-URL.
|
||||
func NameToSubURL(name string) string {
|
||||
return url.PathEscape(strings.ReplaceAll(name, " ", "-"))
|
||||
}
|
||||
|
||||
// NormalizeWikiName normalizes a wiki name
|
||||
func NormalizeWikiName(name string) string {
|
||||
return strings.ReplaceAll(name, "-", " ")
|
||||
}
|
||||
|
||||
// NameToFilename converts a wiki name to its corresponding filename.
|
||||
func NameToFilename(name, path string) string {
|
||||
name = strings.ReplaceAll(name, " ", "-")
|
||||
return path + url.QueryEscape(name) + ".md"
|
||||
}
|
||||
|
||||
// NameToDirName converts a wiki name to its corresponding filename.
|
||||
func NameToDirName(name, path string) string {
|
||||
name = strings.ReplaceAll(name, " ", "-")
|
||||
return path + url.QueryEscape(name) + "/.keep"
|
||||
}
|
||||
|
||||
// FilenameToName converts a wiki filename to its corresponding page name.
|
||||
func FilenameToName(filename string) (string, error) {
|
||||
if !strings.HasSuffix(filename, ".md") {
|
||||
return "", repo_model.ErrWikiInvalidFileName{
|
||||
FileName: filename,
|
||||
}
|
||||
}
|
||||
basename := filename[:len(filename)-3]
|
||||
unescaped, err := url.QueryUnescape(basename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return NormalizeWikiName(unescaped), nil
|
||||
}
|
||||
|
||||
// InitWiki initializes a wiki for repository,
|
||||
// it does nothing when repository already has wiki.
|
||||
func InitWiki(ctx context.Context, repo *repo_model.Repository) error {
|
||||
if repo.HasWiki() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := git.InitRepository(ctx, repo.WikiPath(), true); err != nil {
|
||||
return fmt.Errorf("InitRepository: %w", err)
|
||||
} else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
|
||||
return fmt.Errorf("createDelegateHooks: %w", err)
|
||||
} else if _, _, err = git.NewCommand(ctx, "symbolic-ref", "HEAD", git.BranchPrefix+DefaultBranch).RunStdString(&git.RunOpts{Dir: repo.WikiPath()}); err != nil {
|
||||
return fmt.Errorf("unable to set default wiki branch to master: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareWikiFileName try to find a suitable file path with file name by the given raw wiki name.
|
||||
// return: existence, prepared file path with name, error
|
||||
func prepareWikiFileName(gitRepo *git.Repository, wikiName, path string) (bool, string, error) {
|
||||
unescaped := path + wikiName + ".md"
|
||||
escaped := NameToFilename(wikiName, path)
|
||||
|
||||
// Look for both files
|
||||
filesInIndex, err := gitRepo.LsTree(DefaultBranch, unescaped, escaped)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Not a valid object name master") {
|
||||
return false, escaped, nil
|
||||
}
|
||||
log.Error("%v", err)
|
||||
return false, escaped, err
|
||||
}
|
||||
|
||||
foundEscaped := false
|
||||
for _, filename := range filesInIndex {
|
||||
switch filename {
|
||||
case unescaped:
|
||||
// if we find the unescaped file return it
|
||||
return true, unescaped, nil
|
||||
case escaped:
|
||||
foundEscaped = true
|
||||
}
|
||||
}
|
||||
|
||||
// If not return whether the escaped file exists, and the escaped filename to keep backwards compatibility.
|
||||
return foundEscaped, escaped, nil
|
||||
}
|
||||
|
||||
// prepareWikiDirName try to find a suitable file path with file name by the given raw wiki name.
|
||||
// return: existence, prepared file path with name, error
|
||||
func prepareWikiDirName(gitRepo *git.Repository, wikiName, path string) (bool, string, error) {
|
||||
unescaped := path + wikiName + "/.keep"
|
||||
escaped := NameToDirName(wikiName, path)
|
||||
|
||||
// Look for both files
|
||||
filesInIndex, err := gitRepo.LsTree(DefaultBranch, unescaped, escaped)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Not a valid object name master") {
|
||||
return false, escaped, nil
|
||||
}
|
||||
log.Error("%v", err)
|
||||
return false, escaped, err
|
||||
}
|
||||
|
||||
foundEscaped := false
|
||||
for _, filename := range filesInIndex {
|
||||
switch filename {
|
||||
case unescaped:
|
||||
// if we find the unescaped file return it
|
||||
return true, unescaped, nil
|
||||
case escaped:
|
||||
foundEscaped = true
|
||||
}
|
||||
}
|
||||
|
||||
// If not return whether the escaped file exists, and the escaped filename to keep backwards compatibility.
|
||||
return foundEscaped, escaped, nil
|
||||
}
|
||||
|
||||
// updateWikiPage adds a new page or edits an existing page in repository wiki.
|
||||
func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName, path, content, message string, isNew bool, isDir bool) (err error) {
|
||||
if err = nameAllowed(newWikiName); err != nil {
|
||||
return err
|
||||
}
|
||||
wikiWorkingPool.CheckIn(fmt.Sprint(repo.ID))
|
||||
defer wikiWorkingPool.CheckOut(fmt.Sprint(repo.ID))
|
||||
|
||||
if err = InitWiki(ctx, repo); err != nil {
|
||||
return fmt.Errorf("InitWiki: %w", err)
|
||||
}
|
||||
|
||||
hasMasterBranch := git.IsBranchExist(ctx, repo.WikiPath(), DefaultBranch)
|
||||
|
||||
basePath, err := repo_module.CreateTemporaryPath("update-wiki")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := repo_module.RemoveTemporaryPath(basePath); err != nil {
|
||||
log.Error("Merge: RemoveTemporaryPath: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
cloneOpts := git.CloneRepoOptions{
|
||||
Bare: true,
|
||||
Shared: true,
|
||||
}
|
||||
|
||||
if hasMasterBranch {
|
||||
cloneOpts.Branch = DefaultBranch
|
||||
}
|
||||
|
||||
if err := git.Clone(ctx, 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 (%w)", repo.FullName(), err)
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, 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 %w", 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 %w", basePath, err)
|
||||
}
|
||||
}
|
||||
var isWikiExist bool
|
||||
var newWikiPath string
|
||||
if !isDir {
|
||||
isWikiExist, newWikiPath, err = prepareWikiFileName(gitRepo, newWikiName, path)
|
||||
} else {
|
||||
isWikiExist, newWikiPath, err = prepareWikiDirName(gitRepo, newWikiName, path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isNew {
|
||||
if isWikiExist {
|
||||
return repo_model.ErrWikiAlreadyExist{
|
||||
Title: newWikiPath,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// avoid check existence again if wiki name is not changed since gitRepo.LsFiles(...) is not free.
|
||||
isOldWikiExist := true
|
||||
oldWikiPath := newWikiPath
|
||||
if oldWikiName != newWikiName {
|
||||
isOldWikiExist, oldWikiPath, err = prepareWikiFileName(gitRepo, oldWikiName, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isOldWikiExist {
|
||||
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,
|
||||
}
|
||||
|
||||
committer := doer.NewGitSig()
|
||||
|
||||
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo.WikiPath(), doer)
|
||||
if sign {
|
||||
commitTreeOpts.KeyID = signingKey
|
||||
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
||||
committer = signer
|
||||
}
|
||||
} else {
|
||||
commitTreeOpts.NoGPGSign = true
|
||||
}
|
||||
if hasMasterBranch {
|
||||
commitTreeOpts.Parents = []string{"HEAD"}
|
||||
}
|
||||
|
||||
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
|
||||
Remote: DefaultRemote,
|
||||
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, DefaultBranch),
|
||||
Env: repo_module.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: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWikiPage adds a new wiki page with a given wikiPath.
|
||||
func AddWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName, path, content, message string) error {
|
||||
return updateWikiPage(ctx, doer, repo, "", wikiName, path, content, message, true, false)
|
||||
}
|
||||
|
||||
// AddWikiDir adds a new wiki page with a given wikiPath.
|
||||
func AddWikiDir(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName, path, message string) error {
|
||||
return updateWikiPage(ctx, doer, repo, "", wikiName, path, "", message, true, true)
|
||||
}
|
||||
|
||||
// EditWikiPage updates a wiki page identified by its wikiPath,
|
||||
// optionally also changing wikiPath.
|
||||
func EditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldWikiName, newWikiName, content, message string) error {
|
||||
return updateWikiPage(ctx, doer, repo, oldWikiName, newWikiName, "", content, message, false, false)
|
||||
}
|
||||
|
||||
// DeleteWikiPage deletes a wiki page identified by its path.
|
||||
func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, wikiName string) (err error) {
|
||||
wikiWorkingPool.CheckIn(fmt.Sprint(repo.ID))
|
||||
defer wikiWorkingPool.CheckOut(fmt.Sprint(repo.ID))
|
||||
|
||||
if err = InitWiki(ctx, repo); err != nil {
|
||||
return fmt.Errorf("InitWiki: %w", err)
|
||||
}
|
||||
|
||||
basePath, err := repo_module.CreateTemporaryPath("update-wiki")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := repo_module.RemoveTemporaryPath(basePath); err != nil {
|
||||
log.Error("Merge: RemoveTemporaryPath: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{
|
||||
Bare: true,
|
||||
Shared: true,
|
||||
Branch: DefaultBranch,
|
||||
}); err != nil {
|
||||
log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
|
||||
return fmt.Errorf("Failed to clone repository: %s (%w)", repo.FullName(), err)
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, 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 %w", basePath, err)
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
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 %w", basePath, err)
|
||||
}
|
||||
// todo:fix path
|
||||
found, wikiPath, err := prepareWikiFileName(gitRepo, wikiName, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found {
|
||||
err := gitRepo.RemoveFilesFromIndex(wikiPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
// FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
|
||||
|
||||
tree, err := gitRepo.WriteTree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message := "Delete page '" + wikiName + "'"
|
||||
commitTreeOpts := git.CommitTreeOpts{
|
||||
Message: message,
|
||||
Parents: []string{"HEAD"},
|
||||
}
|
||||
|
||||
committer := doer.NewGitSig()
|
||||
|
||||
sign, signingKey, signer, _ := asymkey_service.SignWikiCommit(ctx, repo.WikiPath(), doer)
|
||||
if sign {
|
||||
commitTreeOpts.KeyID = signingKey
|
||||
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
|
||||
committer = signer
|
||||
}
|
||||
} else {
|
||||
commitTreeOpts.NoGPGSign = true
|
||||
}
|
||||
|
||||
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := git.Push(gitRepo.Ctx, basePath, git.PushOptions{
|
||||
Remote: DefaultRemote,
|
||||
Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, DefaultBranch),
|
||||
Env: repo_module.PushingEnvironment(doer, repo),
|
||||
}); err != nil {
|
||||
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("Push: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteWiki removes the actual and local copy of repository wiki.
|
||||
func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
|
||||
if err := repo_model.UpdateRepositoryUnits(repo, nil, []unit.Type{unit.TypeWiki}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue