]> source.dussan.org Git - gitea.git/commitdiff
Move wiki related funtions from models to services/wiki (#9355)
authorLunny Xiao <xiaolunwen@gmail.com>
Tue, 7 Jan 2020 18:27:36 +0000 (02:27 +0800)
committerzeripath <art27@cantab.net>
Tue, 7 Jan 2020 18:27:36 +0000 (18:27 +0000)
* Move wiki related funtions from models to services/wiki

models/wiki.go
models/wiki_test.go
routers/private/serv.go
routers/repo/wiki.go
routers/repo/wiki_test.go
services/wiki/wiki.go [new file with mode: 0644]
services/wiki/wiki_test.go [new file with mode: 0644]

index 8b63716afa1f0d8b8f092239bfabf544d8e4ef43..32a0cc1627a6327bab9d20ec1ceacb0ce184b66a 100644 (file)
@@ -5,53 +5,12 @@
 package models
 
 import (
-       "fmt"
-       "net/url"
-       "os"
        "path/filepath"
        "strings"
 
-       "code.gitea.io/gitea/modules/git"
-       "code.gitea.io/gitea/modules/log"
-       "code.gitea.io/gitea/modules/sync"
-
        "github.com/unknwon/com"
 )
 
-var (
-       reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
-       wikiWorkingPool   = sync.NewExclusivePool()
-)
-
-// NormalizeWikiName normalizes a wiki name
-func NormalizeWikiName(name string) string {
-       return strings.Replace(name, "-", " ", -1)
-}
-
-// WikiNameToSubURL converts a wiki name to its corresponding sub-URL.
-func WikiNameToSubURL(name string) string {
-       return url.QueryEscape(strings.Replace(name, " ", "-", -1))
-}
-
-// WikiNameToFilename converts a wiki name to its corresponding filename.
-func WikiNameToFilename(name string) string {
-       name = strings.Replace(name, " ", "-", -1)
-       return url.QueryEscape(name) + ".md"
-}
-
-// WikiFilenameToName converts a wiki filename to its corresponding page name.
-func WikiFilenameToName(filename string) (string, error) {
-       if !strings.HasSuffix(filename, ".md") {
-               return "", ErrWikiInvalidFileName{filename}
-       }
-       basename := filename[:len(filename)-3]
-       unescaped, err := url.QueryUnescape(basename)
-       if err != nil {
-               return "", err
-       }
-       return NormalizeWikiName(unescaped), nil
-}
-
 // WikiCloneLink returns clone URLs of repository wiki.
 func (repo *Repository) WikiCloneLink() *CloneLink {
        return repo.cloneLink(x, true)
@@ -71,275 +30,3 @@ func (repo *Repository) WikiPath() string {
 func (repo *Repository) HasWiki() bool {
        return com.IsDir(repo.WikiPath())
 }
-
-// InitWiki initializes a wiki for repository,
-// it does nothing when repository already has wiki.
-func (repo *Repository) InitWiki() error {
-       if repo.HasWiki() {
-               return nil
-       }
-
-       if err := git.InitRepository(repo.WikiPath(), true); err != nil {
-               return fmt.Errorf("InitRepository: %v", err)
-       } else if err = createDelegateHooks(repo.WikiPath()); err != nil {
-               return fmt.Errorf("createDelegateHooks: %v", err)
-       }
-       return nil
-}
-
-// nameAllowed checks if a wiki name is allowed
-func nameAllowed(name string) error {
-       for _, reservedName := range reservedWikiNames {
-               if name == reservedName {
-                       return ErrWikiReservedName{name}
-               }
-       }
-       return nil
-}
-
-// updateWikiPage adds a new page to the repository wiki.
-func (repo *Repository) updateWikiPage(doer *User, 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 = repo.InitWiki(); err != nil {
-               return fmt.Errorf("InitWiki: %v", err)
-       }
-
-       hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master")
-
-       basePath, err := CreateTemporaryPath("update-wiki")
-       if err != nil {
-               return err
-       }
-       defer func() {
-               if err := 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 := WikiNameToFilename(newWikiName)
-       if isNew {
-               filesInIndex, err := gitRepo.LsFiles(newWikiPath)
-               if err != nil {
-                       log.Error("%v", err)
-                       return err
-               }
-               for _, file := range filesInIndex {
-                       if file == newWikiPath {
-                               return ErrWikiAlreadyExist{newWikiPath}
-                       }
-               }
-       } else {
-               oldWikiPath := WikiNameToFilename(oldWikiName)
-               filesInIndex, err := gitRepo.LsFiles(oldWikiPath)
-               if err != nil {
-                       log.Error("%v", err)
-                       return err
-               }
-               found := false
-               for _, file := range filesInIndex {
-                       if file == oldWikiPath {
-                               found = true
-                               break
-                       }
-               }
-               if found {
-                       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: FullPushingEnvironment(
-                       doer,
-                       doer,
-                       repo,
-                       repo.Name+".wiki",
-                       0,
-               ),
-       }); err != nil {
-               log.Error("%v", err)
-               return fmt.Errorf("Push: %v", err)
-       }
-
-       return nil
-}
-
-// AddWikiPage adds a new wiki page with a given wikiPath.
-func (repo *Repository) AddWikiPage(doer *User, wikiName, content, message string) error {
-       return repo.updateWikiPage(doer, "", wikiName, content, message, true)
-}
-
-// EditWikiPage updates a wiki page identified by its wikiPath,
-// optionally also changing wikiPath.
-func (repo *Repository) EditWikiPage(doer *User, oldWikiName, newWikiName, content, message string) error {
-       return repo.updateWikiPage(doer, oldWikiName, newWikiName, content, message, false)
-}
-
-// DeleteWikiPage deletes a wiki page identified by its path.
-func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) {
-       wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
-       defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
-
-       if err = repo.InitWiki(); err != nil {
-               return fmt.Errorf("InitWiki: %v", err)
-       }
-
-       basePath, err := CreateTemporaryPath("update-wiki")
-       if err != nil {
-               return err
-       }
-       defer func() {
-               if err := RemoveTemporaryPath(basePath); err != nil {
-                       log.Error("Merge: RemoveTemporaryPath: %s", err)
-               }
-       }()
-
-       if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{
-               Bare:   true,
-               Shared: true,
-               Branch: "master",
-       }); 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 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)
-       }
-
-       wikiPath := WikiNameToFilename(wikiName)
-       filesInIndex, err := gitRepo.LsFiles(wikiPath)
-       found := false
-       for _, file := range filesInIndex {
-               if file == wikiPath {
-                       found = true
-                       break
-               }
-       }
-       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"},
-       }
-
-       sign, signingKey := repo.SignWikiCommit(doer)
-       if sign {
-               commitTreeOpts.KeyID = signingKey
-       } else {
-               commitTreeOpts.NoGPGSign = true
-       }
-
-       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
-       if err != nil {
-               return err
-       }
-
-       if err := git.Push(basePath, git.PushOptions{
-               Remote: "origin",
-               Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
-               Env:    PushingEnvironment(doer, repo),
-       }); err != nil {
-               return fmt.Errorf("Push: %v", err)
-       }
-
-       return nil
-}
index 37c0a86635ea7c44b6ee4e6f74433ffc397f8a1c..244c4f2553b21a651162a8d78b584028a4d85b08 100644 (file)
@@ -8,100 +8,11 @@ import (
        "path/filepath"
        "testing"
 
-       "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/setting"
 
        "github.com/stretchr/testify/assert"
 )
 
-func TestNormalizeWikiName(t *testing.T) {
-       type test struct {
-               Expected string
-               WikiName string
-       }
-       for _, test := range []test{
-               {"wiki name", "wiki name"},
-               {"wiki name", "wiki-name"},
-               {"name with/slash", "name with/slash"},
-               {"name with%percent", "name-with%percent"},
-               {"%2F", "%2F"},
-       } {
-               assert.Equal(t, test.Expected, NormalizeWikiName(test.WikiName))
-       }
-}
-
-func TestWikiNameToFilename(t *testing.T) {
-       type test struct {
-               Expected string
-               WikiName string
-       }
-       for _, test := range []test{
-               {"wiki-name.md", "wiki name"},
-               {"wiki-name.md", "wiki-name"},
-               {"name-with%2Fslash.md", "name with/slash"},
-               {"name-with%25percent.md", "name with%percent"},
-       } {
-               assert.Equal(t, test.Expected, WikiNameToFilename(test.WikiName))
-       }
-}
-
-func TestWikiNameToSubURL(t *testing.T) {
-       type test struct {
-               Expected string
-               WikiName string
-       }
-       for _, test := range []test{
-               {"wiki-name", "wiki name"},
-               {"wiki-name", "wiki-name"},
-               {"name-with%2Fslash", "name with/slash"},
-               {"name-with%25percent", "name with%percent"},
-       } {
-               assert.Equal(t, test.Expected, WikiNameToSubURL(test.WikiName))
-       }
-}
-
-func TestWikiFilenameToName(t *testing.T) {
-       type test struct {
-               Expected string
-               Filename string
-       }
-       for _, test := range []test{
-               {"hello world", "hello-world.md"},
-               {"symbols/?*", "symbols%2F%3F%2A.md"},
-       } {
-               name, err := WikiFilenameToName(test.Filename)
-               assert.NoError(t, err)
-               assert.Equal(t, test.Expected, name)
-       }
-       for _, badFilename := range []string{
-               "nofileextension",
-               "wrongfileextension.txt",
-       } {
-               _, err := WikiFilenameToName(badFilename)
-               assert.Error(t, err)
-               assert.True(t, IsErrWikiInvalidFileName(err))
-       }
-       _, err := WikiFilenameToName("badescaping%%.md")
-       assert.Error(t, err)
-       assert.False(t, IsErrWikiInvalidFileName(err))
-}
-
-func TestWikiNameToFilenameToName(t *testing.T) {
-       // converting from wiki name to filename, then back to wiki name should
-       // return the original (normalized) name
-       for _, name := range []string{
-               "wiki-name",
-               "wiki name",
-               "wiki name with/slash",
-               "$$$%%%^^&&!@#$(),.<>",
-       } {
-               filename := WikiNameToFilename(name)
-               resultName, err := WikiFilenameToName(filename)
-               assert.NoError(t, err)
-               assert.Equal(t, NormalizeWikiName(name), resultName)
-       }
-}
-
 func TestRepository_WikiCloneLink(t *testing.T) {
        assert.NoError(t, PrepareTestDatabase())
 
@@ -131,107 +42,3 @@ func TestRepository_HasWiki(t *testing.T) {
        repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
        assert.False(t, repo2.HasWiki())
 }
-
-func TestRepository_InitWiki(t *testing.T) {
-       PrepareTestEnv(t)
-       // repo1 already has a wiki
-       repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-       assert.NoError(t, repo1.InitWiki())
-
-       // repo2 does not already have a wiki
-       repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository)
-       assert.NoError(t, repo2.InitWiki())
-       assert.True(t, repo2.HasWiki())
-}
-
-func TestRepository_AddWikiPage(t *testing.T) {
-       assert.NoError(t, PrepareTestDatabase())
-       const wikiContent = "This is the wiki content"
-       const commitMsg = "Commit message"
-       repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-       doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-       for _, wikiName := range []string{
-               "Another page",
-               "Here's a <tag> and a/slash",
-       } {
-               wikiName := wikiName
-               t.Run("test wiki exist: "+wikiName, func(t *testing.T) {
-                       t.Parallel()
-                       assert.NoError(t, repo.AddWikiPage(doer, wikiName, wikiContent, commitMsg))
-                       // Now need to show that the page has been added:
-                       gitRepo, err := git.OpenRepository(repo.WikiPath())
-                       assert.NoError(t, err)
-                       defer gitRepo.Close()
-                       masterTree, err := gitRepo.GetTree("master")
-                       assert.NoError(t, err)
-                       wikiPath := WikiNameToFilename(wikiName)
-                       entry, err := masterTree.GetTreeEntryByPath(wikiPath)
-                       assert.NoError(t, err)
-                       assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName)
-               })
-       }
-
-       t.Run("check wiki already exist", func(t *testing.T) {
-               t.Parallel()
-               // test for already-existing wiki name
-               err := repo.AddWikiPage(doer, "Home", wikiContent, commitMsg)
-               assert.Error(t, err)
-               assert.True(t, IsErrWikiAlreadyExist(err))
-       })
-
-       t.Run("check wiki reserved name", func(t *testing.T) {
-               t.Parallel()
-               // test for reserved wiki name
-               err := repo.AddWikiPage(doer, "_edit", wikiContent, commitMsg)
-               assert.Error(t, err)
-               assert.True(t, IsErrWikiReservedName(err))
-       })
-}
-
-func TestRepository_EditWikiPage(t *testing.T) {
-       const newWikiContent = "This is the new content"
-       const commitMsg = "Commit message"
-       repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-       doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-       for _, newWikiName := range []string{
-               "Home", // same name as before
-               "New home",
-               "New/name/with/slashes",
-       } {
-               PrepareTestEnv(t)
-               assert.NoError(t, repo.EditWikiPage(doer, "Home", newWikiName, newWikiContent, commitMsg))
-
-               // Now need to show that the page has been added:
-               gitRepo, err := git.OpenRepository(repo.WikiPath())
-               assert.NoError(t, err)
-               masterTree, err := gitRepo.GetTree("master")
-               assert.NoError(t, err)
-               wikiPath := WikiNameToFilename(newWikiName)
-               entry, err := masterTree.GetTreeEntryByPath(wikiPath)
-               assert.NoError(t, err)
-               assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName)
-
-               if newWikiName != "Home" {
-                       _, err := masterTree.GetTreeEntryByPath("Home.md")
-                       assert.Error(t, err)
-               }
-               gitRepo.Close()
-       }
-}
-
-func TestRepository_DeleteWikiPage(t *testing.T) {
-       PrepareTestEnv(t)
-       repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
-       doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
-       assert.NoError(t, repo.DeleteWikiPage(doer, "Home"))
-
-       // Now need to show that the page has been added:
-       gitRepo, err := git.OpenRepository(repo.WikiPath())
-       assert.NoError(t, err)
-       defer gitRepo.Close()
-       masterTree, err := gitRepo.GetTree("master")
-       assert.NoError(t, err)
-       wikiPath := WikiNameToFilename("Home")
-       _, err = masterTree.GetTreeEntryByPath(wikiPath)
-       assert.Error(t, err)
-}
index 64fd671309ff24e8e19fd7b6c1c81328aee74c49..c769f30d07ca7ff90c0154d9e19c83ee7ae63ce5 100644 (file)
@@ -15,6 +15,7 @@ import (
        "code.gitea.io/gitea/modules/private"
        "code.gitea.io/gitea/modules/setting"
        repo_service "code.gitea.io/gitea/services/repository"
+       wiki_service "code.gitea.io/gitea/services/wiki"
 
        "gitea.com/macaron/macaron"
 )
@@ -320,7 +321,7 @@ func ServCommand(ctx *macaron.Context) {
 
        // Finally if we're trying to touch the wiki we should init it
        if results.IsWiki {
-               if err = repo.InitWiki(); err != nil {
+               if err = wiki_service.InitWiki(repo); err != nil {
                        log.Error("Failed to initialize the wiki in %-v Error: %v", repo, err)
                        ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
                                "results": results,
index 6cf194365891fe38d3c13cf22a547aeeae8fb9e2..b0bd8047b8b36eade13021b25649722663c2b6dc 100644 (file)
@@ -22,6 +22,7 @@ import (
        "code.gitea.io/gitea/modules/markup/markdown"
        "code.gitea.io/gitea/modules/timeutil"
        "code.gitea.io/gitea/modules/util"
+       wiki_service "code.gitea.io/gitea/services/wiki"
 )
 
 const (
@@ -124,7 +125,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
 func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
        var entry *git.TreeEntry
        var err error
-       pageFilename := models.WikiNameToFilename(wikiName)
+       pageFilename := wiki_service.NameToFilename(wikiName)
        if entry, err = findEntryForFile(commit, pageFilename); err != nil {
                ctx.ServerError("findEntryForFile", err)
                return nil, nil, "", false
@@ -157,7 +158,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
                if !entry.IsRegular() {
                        continue
                }
-               wikiName, err := models.WikiFilenameToName(entry.Name())
+               wikiName, err := wiki_service.FilenameToName(entry.Name())
                if err != nil {
                        if models.IsErrWikiInvalidFileName(err) {
                                continue
@@ -172,17 +173,17 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
                }
                pages = append(pages, PageMeta{
                        Name:   wikiName,
-                       SubURL: models.WikiNameToSubURL(wikiName),
+                       SubURL: wiki_service.NameToSubURL(wikiName),
                })
        }
        ctx.Data["Pages"] = pages
 
        // get requested pagename
-       pageName := models.NormalizeWikiName(ctx.Params(":page"))
+       pageName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
        if len(pageName) == 0 {
                pageName = "Home"
        }
-       ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
+       ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
        ctx.Data["old_title"] = pageName
        ctx.Data["Title"] = pageName
        ctx.Data["title"] = pageName
@@ -243,11 +244,11 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
        }
 
        // get requested pagename
-       pageName := models.NormalizeWikiName(ctx.Params(":page"))
+       pageName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
        if len(pageName) == 0 {
                pageName = "Home"
        }
-       ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
+       ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
        ctx.Data["old_title"] = pageName
        ctx.Data["Title"] = pageName
        ctx.Data["title"] = pageName
@@ -320,11 +321,11 @@ func renderEditPage(ctx *context.Context) {
        }()
 
        // get requested pagename
-       pageName := models.NormalizeWikiName(ctx.Params(":page"))
+       pageName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
        if len(pageName) == 0 {
                pageName = "Home"
        }
-       ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
+       ctx.Data["PageURL"] = wiki_service.NameToSubURL(pageName)
        ctx.Data["old_title"] = pageName
        ctx.Data["Title"] = pageName
        ctx.Data["title"] = pageName
@@ -474,7 +475,7 @@ func WikiPages(ctx *context.Context) {
                        ctx.ServerError("GetCommit", err)
                        return
                }
-               wikiName, err := models.WikiFilenameToName(entry.Name())
+               wikiName, err := wiki_service.FilenameToName(entry.Name())
                if err != nil {
                        if models.IsErrWikiInvalidFileName(err) {
                                continue
@@ -488,7 +489,7 @@ func WikiPages(ctx *context.Context) {
                }
                pages = append(pages, PageMeta{
                        Name:        wikiName,
-                       SubURL:      models.WikiNameToSubURL(wikiName),
+                       SubURL:      wiki_service.NameToSubURL(wikiName),
                        UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
                })
        }
@@ -528,7 +529,7 @@ func WikiRaw(ctx *context.Context) {
                                providedPath = providedPath[:len(providedPath)-3]
                        }
 
-                       wikiPath := models.WikiNameToFilename(providedPath)
+                       wikiPath := wiki_service.NameToFilename(providedPath)
                        entry, err = findEntryForFile(commit, wikiPath)
                        if err != nil {
                                ctx.ServerError("findFile", err)
@@ -576,8 +577,8 @@ func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
                return
        }
 
-       wikiName := models.NormalizeWikiName(form.Title)
-       if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil {
+       wikiName := wiki_service.NormalizeWikiName(form.Title)
+       if err := wiki_service.AddWikiPage(ctx.User, ctx.Repo.Repository, 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)
@@ -590,7 +591,7 @@ func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
                return
        }
 
-       ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName))
+       ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(wikiName))
 }
 
 // EditWiki render wiki modify page
@@ -623,25 +624,25 @@ func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
                return
        }
 
-       oldWikiName := models.NormalizeWikiName(ctx.Params(":page"))
-       newWikiName := models.NormalizeWikiName(form.Title)
+       oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
+       newWikiName := wiki_service.NormalizeWikiName(form.Title)
 
-       if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
+       if err := wiki_service.EditWikiPage(ctx.User, ctx.Repo.Repository, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
                ctx.ServerError("EditWikiPage", err)
                return
        }
 
-       ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName))
+       ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + wiki_service.NameToSubURL(newWikiName))
 }
 
 // DeleteWikiPagePost delete wiki page
 func DeleteWikiPagePost(ctx *context.Context) {
-       wikiName := models.NormalizeWikiName(ctx.Params(":page"))
+       wikiName := wiki_service.NormalizeWikiName(ctx.Params(":page"))
        if len(wikiName) == 0 {
                wikiName = "Home"
        }
 
-       if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil {
+       if err := wiki_service.DeleteWikiPage(ctx.User, ctx.Repo.Repository, wikiName); err != nil {
                ctx.ServerError("DeleteWikiPage", err)
                return
        }
index 44fcd02035fe39b47c72caffcc01b94424c1d12f..843a8ddf2bb458735b21c276ab221dbf3a9b6892 100644 (file)
@@ -13,6 +13,7 @@ import (
        "code.gitea.io/gitea/modules/auth"
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/test"
+       wiki_service "code.gitea.io/gitea/services/wiki"
 
        "github.com/stretchr/testify/assert"
 )
@@ -29,7 +30,7 @@ func wikiEntry(t *testing.T, repo *models.Repository, wikiName string) *git.Tree
        entries, err := commit.ListEntries()
        assert.NoError(t, err)
        for _, entry := range entries {
-               if entry.Name() == models.WikiNameToFilename(wikiName) {
+               if entry.Name() == wiki_service.NameToFilename(wikiName) {
                        return entry
                }
        }
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
new file mode 100644 (file)
index 0000000..58af203
--- /dev/null
@@ -0,0 +1,322 @@
+// 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 (
+       "fmt"
+       "net/url"
+       "os"
+       "strings"
+
+       "code.gitea.io/gitea/models"
+       "code.gitea.io/gitea/modules/git"
+       "code.gitea.io/gitea/modules/log"
+       "code.gitea.io/gitea/modules/sync"
+       "code.gitea.io/gitea/modules/util"
+
+       "github.com/unknwon/com"
+)
+
+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
+}
+
+// NameToSubURL converts a wiki name to its corresponding sub-URL.
+func NameToSubURL(name string) string {
+       return url.QueryEscape(strings.Replace(name, " ", "-", -1))
+}
+
+// NormalizeWikiName normalizes a wiki name
+func NormalizeWikiName(name string) string {
+       return strings.Replace(name, "-", " ", -1)
+}
+
+// NameToFilename converts a wiki name to its corresponding filename.
+func NameToFilename(name string) string {
+       name = strings.Replace(name, " ", "-", -1)
+       return url.QueryEscape(name) + ".md"
+}
+
+// FilenameToName converts a wiki filename to its corresponding page name.
+func FilenameToName(filename string) (string, error) {
+       if !strings.HasSuffix(filename, ".md") {
+               return "", models.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(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 = models.CreateDelegateHooks(repo.WikiPath()); err != nil {
+               return fmt.Errorf("createDelegateHooks: %v", err)
+       }
+       return nil
+}
+
+// updateWikiPage adds a new page to the repository wiki.
+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 := 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 := 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)
+               return fmt.Errorf("Push: %v", err)
+       }
+
+       return nil
+}
+
+// AddWikiPage adds a new wiki page with a given wikiPath.
+func AddWikiPage(doer *models.User, repo *models.Repository, wikiName, content, message string) error {
+       return updateWikiPage(doer, repo, "", wikiName, content, message, true)
+}
+
+// EditWikiPage updates a wiki page identified by its wikiPath,
+// optionally also changing wikiPath.
+func EditWikiPage(doer *models.User, repo *models.Repository, oldWikiName, newWikiName, content, message string) error {
+       return updateWikiPage(doer, repo, oldWikiName, newWikiName, content, message, false)
+}
+
+// DeleteWikiPage deletes a wiki page identified by its path.
+func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string) (err error) {
+       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)
+       }
+
+       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)
+               }
+       }()
+
+       if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{
+               Bare:   true,
+               Shared: true,
+               Branch: "master",
+       }); 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 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)
+       }
+
+       wikiPath := NameToFilename(wikiName)
+       filesInIndex, err := gitRepo.LsFiles(wikiPath)
+       found := false
+       for _, file := range filesInIndex {
+               if file == wikiPath {
+                       found = true
+                       break
+               }
+       }
+       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"},
+       }
+
+       sign, signingKey := repo.SignWikiCommit(doer)
+       if sign {
+               commitTreeOpts.KeyID = signingKey
+       } else {
+               commitTreeOpts.NoGPGSign = true
+       }
+
+       commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
+       if err != nil {
+               return err
+       }
+
+       if err := git.Push(basePath, git.PushOptions{
+               Remote: "origin",
+               Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
+               Env:    models.PushingEnvironment(doer, repo),
+       }); err != nil {
+               return fmt.Errorf("Push: %v", err)
+       }
+
+       return nil
+}
diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go
new file mode 100644 (file)
index 0000000..0e1d460
--- /dev/null
@@ -0,0 +1,210 @@
+// 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 (
+       "path/filepath"
+       "testing"
+
+       "code.gitea.io/gitea/models"
+       "code.gitea.io/gitea/modules/git"
+       "github.com/stretchr/testify/assert"
+)
+
+func TestMain(m *testing.M) {
+       models.MainTest(m, filepath.Join("..", ".."))
+}
+
+func TestWikiNameToSubURL(t *testing.T) {
+       type test struct {
+               Expected string
+               WikiName string
+       }
+       for _, test := range []test{
+               {"wiki-name", "wiki name"},
+               {"wiki-name", "wiki-name"},
+               {"name-with%2Fslash", "name with/slash"},
+               {"name-with%25percent", "name with%percent"},
+       } {
+               assert.Equal(t, test.Expected, NameToSubURL(test.WikiName))
+       }
+}
+
+func TestNormalizeWikiName(t *testing.T) {
+       type test struct {
+               Expected string
+               WikiName string
+       }
+       for _, test := range []test{
+               {"wiki name", "wiki name"},
+               {"wiki name", "wiki-name"},
+               {"name with/slash", "name with/slash"},
+               {"name with%percent", "name-with%percent"},
+               {"%2F", "%2F"},
+       } {
+               assert.Equal(t, test.Expected, NormalizeWikiName(test.WikiName))
+       }
+}
+
+func TestWikiNameToFilename(t *testing.T) {
+       type test struct {
+               Expected string
+               WikiName string
+       }
+       for _, test := range []test{
+               {"wiki-name.md", "wiki name"},
+               {"wiki-name.md", "wiki-name"},
+               {"name-with%2Fslash.md", "name with/slash"},
+               {"name-with%25percent.md", "name with%percent"},
+       } {
+               assert.Equal(t, test.Expected, NameToFilename(test.WikiName))
+       }
+}
+
+func TestWikiFilenameToName(t *testing.T) {
+       type test struct {
+               Expected string
+               Filename string
+       }
+       for _, test := range []test{
+               {"hello world", "hello-world.md"},
+               {"symbols/?*", "symbols%2F%3F%2A.md"},
+       } {
+               name, err := FilenameToName(test.Filename)
+               assert.NoError(t, err)
+               assert.Equal(t, test.Expected, name)
+       }
+       for _, badFilename := range []string{
+               "nofileextension",
+               "wrongfileextension.txt",
+       } {
+               _, err := FilenameToName(badFilename)
+               assert.Error(t, err)
+               assert.True(t, models.IsErrWikiInvalidFileName(err))
+       }
+       _, err := FilenameToName("badescaping%%.md")
+       assert.Error(t, err)
+       assert.False(t, models.IsErrWikiInvalidFileName(err))
+}
+
+func TestWikiNameToFilenameToName(t *testing.T) {
+       // converting from wiki name to filename, then back to wiki name should
+       // return the original (normalized) name
+       for _, name := range []string{
+               "wiki-name",
+               "wiki name",
+               "wiki name with/slash",
+               "$$$%%%^^&&!@#$(),.<>",
+       } {
+               filename := NameToFilename(name)
+               resultName, err := FilenameToName(filename)
+               assert.NoError(t, err)
+               assert.Equal(t, NormalizeWikiName(name), resultName)
+       }
+}
+
+func TestRepository_InitWiki(t *testing.T) {
+       models.PrepareTestEnv(t)
+       // repo1 already has a wiki
+       repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+       assert.NoError(t, InitWiki(repo1))
+
+       // repo2 does not already have a wiki
+       repo2 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository)
+       assert.NoError(t, InitWiki(repo2))
+       assert.True(t, repo2.HasWiki())
+}
+
+func TestRepository_AddWikiPage(t *testing.T) {
+       assert.NoError(t, models.PrepareTestDatabase())
+       const wikiContent = "This is the wiki content"
+       const commitMsg = "Commit message"
+       repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+       doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+       for _, wikiName := range []string{
+               "Another page",
+               "Here's a <tag> and a/slash",
+       } {
+               wikiName := wikiName
+               t.Run("test wiki exist: "+wikiName, func(t *testing.T) {
+                       t.Parallel()
+                       assert.NoError(t, AddWikiPage(doer, repo, wikiName, wikiContent, commitMsg))
+                       // Now need to show that the page has been added:
+                       gitRepo, err := git.OpenRepository(repo.WikiPath())
+                       assert.NoError(t, err)
+                       defer gitRepo.Close()
+                       masterTree, err := gitRepo.GetTree("master")
+                       assert.NoError(t, err)
+                       wikiPath := NameToFilename(wikiName)
+                       entry, err := masterTree.GetTreeEntryByPath(wikiPath)
+                       assert.NoError(t, err)
+                       assert.Equal(t, wikiPath, entry.Name(), "%s not addded correctly", wikiName)
+               })
+       }
+
+       t.Run("check wiki already exist", func(t *testing.T) {
+               t.Parallel()
+               // test for already-existing wiki name
+               err := AddWikiPage(doer, repo, "Home", wikiContent, commitMsg)
+               assert.Error(t, err)
+               assert.True(t, models.IsErrWikiAlreadyExist(err))
+       })
+
+       t.Run("check wiki reserved name", func(t *testing.T) {
+               t.Parallel()
+               // test for reserved wiki name
+               err := AddWikiPage(doer, repo, "_edit", wikiContent, commitMsg)
+               assert.Error(t, err)
+               assert.True(t, models.IsErrWikiReservedName(err))
+       })
+}
+
+func TestRepository_EditWikiPage(t *testing.T) {
+       const newWikiContent = "This is the new content"
+       const commitMsg = "Commit message"
+       repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+       doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+       for _, newWikiName := range []string{
+               "Home", // same name as before
+               "New home",
+               "New/name/with/slashes",
+       } {
+               models.PrepareTestEnv(t)
+               assert.NoError(t, EditWikiPage(doer, repo, "Home", newWikiName, newWikiContent, commitMsg))
+
+               // Now need to show that the page has been added:
+               gitRepo, err := git.OpenRepository(repo.WikiPath())
+               assert.NoError(t, err)
+               masterTree, err := gitRepo.GetTree("master")
+               assert.NoError(t, err)
+               wikiPath := NameToFilename(newWikiName)
+               entry, err := masterTree.GetTreeEntryByPath(wikiPath)
+               assert.NoError(t, err)
+               assert.Equal(t, wikiPath, entry.Name(), "%s not editted correctly", newWikiName)
+
+               if newWikiName != "Home" {
+                       _, err := masterTree.GetTreeEntryByPath("Home.md")
+                       assert.Error(t, err)
+               }
+               gitRepo.Close()
+       }
+}
+
+func TestRepository_DeleteWikiPage(t *testing.T) {
+       models.PrepareTestEnv(t)
+       repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
+       doer := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
+       assert.NoError(t, DeleteWikiPage(doer, repo, "Home"))
+
+       // Now need to show that the page has been added:
+       gitRepo, err := git.OpenRepository(repo.WikiPath())
+       assert.NoError(t, err)
+       defer gitRepo.Close()
+       masterTree, err := gitRepo.GetTree("master")
+       assert.NoError(t, err)
+       wikiPath := NameToFilename("Home")
+       _, err = masterTree.GetTreeEntryByPath(wikiPath)
+       assert.Error(t, err)
+}