You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

wiki.go 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/sync"
  14. "github.com/unknwon/com"
  15. )
  16. var (
  17. reservedWikiNames = []string{"_pages", "_new", "_edit", "raw"}
  18. wikiWorkingPool = sync.NewExclusivePool()
  19. )
  20. // NormalizeWikiName normalizes a wiki name
  21. func NormalizeWikiName(name string) string {
  22. return strings.Replace(name, "-", " ", -1)
  23. }
  24. // WikiNameToSubURL converts a wiki name to its corresponding sub-URL.
  25. func WikiNameToSubURL(name string) string {
  26. return url.QueryEscape(strings.Replace(name, " ", "-", -1))
  27. }
  28. // WikiNameToFilename converts a wiki name to its corresponding filename.
  29. func WikiNameToFilename(name string) string {
  30. name = strings.Replace(name, " ", "-", -1)
  31. return url.QueryEscape(name) + ".md"
  32. }
  33. // WikiFilenameToName converts a wiki filename to its corresponding page name.
  34. func WikiFilenameToName(filename string) (string, error) {
  35. if !strings.HasSuffix(filename, ".md") {
  36. return "", ErrWikiInvalidFileName{filename}
  37. }
  38. basename := filename[:len(filename)-3]
  39. unescaped, err := url.QueryUnescape(basename)
  40. if err != nil {
  41. return "", err
  42. }
  43. return NormalizeWikiName(unescaped), nil
  44. }
  45. // WikiCloneLink returns clone URLs of repository wiki.
  46. func (repo *Repository) WikiCloneLink() *CloneLink {
  47. return repo.cloneLink(x, true)
  48. }
  49. // WikiPath returns wiki data path by given user and repository name.
  50. func WikiPath(userName, repoName string) string {
  51. return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".wiki.git")
  52. }
  53. // WikiPath returns wiki data path for given repository.
  54. func (repo *Repository) WikiPath() string {
  55. return WikiPath(repo.MustOwnerName(), repo.Name)
  56. }
  57. // HasWiki returns true if repository has wiki.
  58. func (repo *Repository) HasWiki() bool {
  59. return com.IsDir(repo.WikiPath())
  60. }
  61. // InitWiki initializes a wiki for repository,
  62. // it does nothing when repository already has wiki.
  63. func (repo *Repository) InitWiki() error {
  64. if repo.HasWiki() {
  65. return nil
  66. }
  67. if err := git.InitRepository(repo.WikiPath(), true); err != nil {
  68. return fmt.Errorf("InitRepository: %v", err)
  69. } else if err = createDelegateHooks(repo.WikiPath()); err != nil {
  70. return fmt.Errorf("createDelegateHooks: %v", err)
  71. }
  72. return nil
  73. }
  74. // nameAllowed checks if a wiki name is allowed
  75. func nameAllowed(name string) error {
  76. for _, reservedName := range reservedWikiNames {
  77. if name == reservedName {
  78. return ErrWikiReservedName{name}
  79. }
  80. }
  81. return nil
  82. }
  83. // updateWikiPage adds a new page to the repository wiki.
  84. func (repo *Repository) updateWikiPage(doer *User, oldWikiName, newWikiName, content, message string, isNew bool) (err error) {
  85. if err = nameAllowed(newWikiName); err != nil {
  86. return err
  87. }
  88. wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
  89. defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
  90. if err = repo.InitWiki(); err != nil {
  91. return fmt.Errorf("InitWiki: %v", err)
  92. }
  93. hasMasterBranch := git.IsBranchExist(repo.WikiPath(), "master")
  94. basePath, err := CreateTemporaryPath("update-wiki")
  95. if err != nil {
  96. return err
  97. }
  98. defer func() {
  99. if err := RemoveTemporaryPath(basePath); err != nil {
  100. log.Error("Merge: RemoveTemporaryPath: %s", err)
  101. }
  102. }()
  103. cloneOpts := git.CloneRepoOptions{
  104. Bare: true,
  105. Shared: true,
  106. }
  107. if hasMasterBranch {
  108. cloneOpts.Branch = "master"
  109. }
  110. if err := git.Clone(repo.WikiPath(), basePath, cloneOpts); err != nil {
  111. log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
  112. return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
  113. }
  114. gitRepo, err := git.OpenRepository(basePath)
  115. if err != nil {
  116. log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
  117. return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
  118. }
  119. if hasMasterBranch {
  120. if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
  121. log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)
  122. return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
  123. }
  124. }
  125. newWikiPath := WikiNameToFilename(newWikiName)
  126. if isNew {
  127. filesInIndex, err := gitRepo.LsFiles(newWikiPath)
  128. if err != nil {
  129. log.Error("%v", err)
  130. return err
  131. }
  132. for _, file := range filesInIndex {
  133. if file == newWikiPath {
  134. return ErrWikiAlreadyExist{newWikiPath}
  135. }
  136. }
  137. } else {
  138. oldWikiPath := WikiNameToFilename(oldWikiName)
  139. filesInIndex, err := gitRepo.LsFiles(oldWikiPath)
  140. if err != nil {
  141. log.Error("%v", err)
  142. return err
  143. }
  144. found := false
  145. for _, file := range filesInIndex {
  146. if file == oldWikiPath {
  147. found = true
  148. break
  149. }
  150. }
  151. if found {
  152. err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
  153. if err != nil {
  154. log.Error("%v", err)
  155. return err
  156. }
  157. }
  158. }
  159. // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
  160. objectHash, err := gitRepo.HashObject(strings.NewReader(content))
  161. if err != nil {
  162. log.Error("%v", err)
  163. return err
  164. }
  165. if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil {
  166. log.Error("%v", err)
  167. return err
  168. }
  169. tree, err := gitRepo.WriteTree()
  170. if err != nil {
  171. log.Error("%v", err)
  172. return err
  173. }
  174. commitTreeOpts := git.CommitTreeOpts{
  175. Message: message,
  176. }
  177. if hasMasterBranch {
  178. commitTreeOpts.Parents = []string{"HEAD"}
  179. }
  180. commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
  181. if err != nil {
  182. log.Error("%v", err)
  183. return err
  184. }
  185. if err := git.Push(basePath, git.PushOptions{
  186. Remote: "origin",
  187. Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
  188. Env: FullPushingEnvironment(
  189. doer,
  190. doer,
  191. repo,
  192. repo.Name+".wiki",
  193. 0,
  194. ),
  195. }); err != nil {
  196. log.Error("%v", err)
  197. return fmt.Errorf("Push: %v", err)
  198. }
  199. return nil
  200. }
  201. // AddWikiPage adds a new wiki page with a given wikiPath.
  202. func (repo *Repository) AddWikiPage(doer *User, wikiName, content, message string) error {
  203. return repo.updateWikiPage(doer, "", wikiName, content, message, true)
  204. }
  205. // EditWikiPage updates a wiki page identified by its wikiPath,
  206. // optionally also changing wikiPath.
  207. func (repo *Repository) EditWikiPage(doer *User, oldWikiName, newWikiName, content, message string) error {
  208. return repo.updateWikiPage(doer, oldWikiName, newWikiName, content, message, false)
  209. }
  210. // DeleteWikiPage deletes a wiki page identified by its path.
  211. func (repo *Repository) DeleteWikiPage(doer *User, wikiName string) (err error) {
  212. wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
  213. defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
  214. if err = repo.InitWiki(); err != nil {
  215. return fmt.Errorf("InitWiki: %v", err)
  216. }
  217. basePath, err := CreateTemporaryPath("update-wiki")
  218. if err != nil {
  219. return err
  220. }
  221. defer func() {
  222. if err := RemoveTemporaryPath(basePath); err != nil {
  223. log.Error("Merge: RemoveTemporaryPath: %s", err)
  224. }
  225. }()
  226. if err := git.Clone(repo.WikiPath(), basePath, git.CloneRepoOptions{
  227. Bare: true,
  228. Shared: true,
  229. Branch: "master",
  230. }); err != nil {
  231. log.Error("Failed to clone repository: %s (%v)", repo.FullName(), err)
  232. return fmt.Errorf("Failed to clone repository: %s (%v)", repo.FullName(), err)
  233. }
  234. gitRepo, err := git.OpenRepository(basePath)
  235. if err != nil {
  236. log.Error("Unable to open temporary repository: %s (%v)", basePath, err)
  237. return fmt.Errorf("Failed to open new temporary repository in: %s %v", basePath, err)
  238. }
  239. if err := gitRepo.ReadTreeToIndex("HEAD"); err != nil {
  240. log.Error("Unable to read HEAD tree to index in: %s %v", basePath, err)
  241. return fmt.Errorf("Unable to read HEAD tree to index in: %s %v", basePath, err)
  242. }
  243. wikiPath := WikiNameToFilename(wikiName)
  244. filesInIndex, err := gitRepo.LsFiles(wikiPath)
  245. found := false
  246. for _, file := range filesInIndex {
  247. if file == wikiPath {
  248. found = true
  249. break
  250. }
  251. }
  252. if found {
  253. err := gitRepo.RemoveFilesFromIndex(wikiPath)
  254. if err != nil {
  255. return err
  256. }
  257. } else {
  258. return os.ErrNotExist
  259. }
  260. // FIXME: The wiki doesn't have lfs support at present - if this changes need to check attributes here
  261. tree, err := gitRepo.WriteTree()
  262. if err != nil {
  263. return err
  264. }
  265. message := "Delete page '" + wikiName + "'"
  266. commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, git.CommitTreeOpts{
  267. Message: message,
  268. Parents: []string{"HEAD"},
  269. })
  270. if err != nil {
  271. return err
  272. }
  273. if err := git.Push(basePath, git.PushOptions{
  274. Remote: "origin",
  275. Branch: fmt.Sprintf("%s:%s%s", commitHash.String(), git.BranchPrefix, "master"),
  276. Env: PushingEnvironment(doer, repo),
  277. }); err != nil {
  278. return fmt.Errorf("Push: %v", err)
  279. }
  280. return nil
  281. }