本站源代码
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.

585 lines
15KB

  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "fmt"
  8. "io/ioutil"
  9. "net/url"
  10. "path/filepath"
  11. "strings"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/auth"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/markup"
  19. "code.gitea.io/gitea/modules/markup/markdown"
  20. "code.gitea.io/gitea/modules/timeutil"
  21. "code.gitea.io/gitea/modules/util"
  22. )
  23. const (
  24. tplWikiStart base.TplName = "repo/wiki/start"
  25. tplWikiView base.TplName = "repo/wiki/view"
  26. tplWikiRevision base.TplName = "repo/wiki/revision"
  27. tplWikiNew base.TplName = "repo/wiki/new"
  28. tplWikiPages base.TplName = "repo/wiki/pages"
  29. )
  30. // MustEnableWiki check if wiki is enabled, if external then redirect
  31. func MustEnableWiki(ctx *context.Context) {
  32. if !ctx.Repo.CanRead(models.UnitTypeWiki) &&
  33. !ctx.Repo.CanRead(models.UnitTypeExternalWiki) {
  34. if log.IsTrace() {
  35. log.Trace("Permission Denied: User %-v cannot read %-v or %-v of repo %-v\n"+
  36. "User in repo has Permissions: %-+v",
  37. ctx.User,
  38. models.UnitTypeWiki,
  39. models.UnitTypeExternalWiki,
  40. ctx.Repo.Repository,
  41. ctx.Repo.Permission)
  42. }
  43. ctx.NotFound("MustEnableWiki", nil)
  44. return
  45. }
  46. unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalWiki)
  47. if err == nil {
  48. ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
  49. return
  50. }
  51. }
  52. // PageMeta wiki page meta information
  53. type PageMeta struct {
  54. Name string
  55. SubURL string
  56. UpdatedUnix timeutil.TimeStamp
  57. }
  58. // findEntryForFile finds the tree entry for a target filepath.
  59. func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
  60. entries, err := commit.ListEntries()
  61. if err != nil {
  62. return nil, err
  63. }
  64. // The longest name should be checked first
  65. for _, entry := range entries {
  66. if entry.IsRegular() && entry.Name() == target {
  67. return entry, nil
  68. }
  69. }
  70. // Then the unescaped, shortest alternative
  71. var unescapedTarget string
  72. if unescapedTarget, err = url.QueryUnescape(target); err != nil {
  73. return nil, err
  74. }
  75. for _, entry := range entries {
  76. if entry.IsRegular() && entry.Name() == unescapedTarget {
  77. return entry, nil
  78. }
  79. }
  80. return nil, nil
  81. }
  82. func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
  83. wikiRepo, err := git.OpenRepository(ctx.Repo.Repository.WikiPath())
  84. if err != nil {
  85. ctx.ServerError("OpenRepository", err)
  86. return nil, nil, err
  87. }
  88. commit, err := wikiRepo.GetBranchCommit("master")
  89. if err != nil {
  90. return wikiRepo, nil, err
  91. }
  92. return wikiRepo, commit, nil
  93. }
  94. // wikiContentsByEntry returns the contents of the wiki page referenced by the
  95. // given tree entry. Writes to ctx if an error occurs.
  96. func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
  97. reader, err := entry.Blob().DataAsync()
  98. if err != nil {
  99. ctx.ServerError("Blob.Data", err)
  100. return nil
  101. }
  102. defer reader.Close()
  103. content, err := ioutil.ReadAll(reader)
  104. if err != nil {
  105. ctx.ServerError("ReadAll", err)
  106. return nil
  107. }
  108. return content
  109. }
  110. // wikiContentsByName returns the contents of a wiki page, along with a boolean
  111. // indicating whether the page exists. Writes to ctx if an error occurs.
  112. func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
  113. var entry *git.TreeEntry
  114. var err error
  115. pageFilename := models.WikiNameToFilename(wikiName)
  116. if entry, err = findEntryForFile(commit, pageFilename); err != nil {
  117. ctx.ServerError("findEntryForFile", err)
  118. return nil, nil, "", false
  119. } else if entry == nil {
  120. return nil, nil, "", true
  121. }
  122. return wikiContentsByEntry(ctx, entry), entry, pageFilename, false
  123. }
  124. func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
  125. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  126. if err != nil {
  127. if !git.IsErrNotExist(err) {
  128. ctx.ServerError("GetBranchCommit", err)
  129. }
  130. return nil, nil
  131. }
  132. // Get page list.
  133. entries, err := commit.ListEntries()
  134. if err != nil {
  135. ctx.ServerError("ListEntries", err)
  136. return nil, nil
  137. }
  138. pages := make([]PageMeta, 0, len(entries))
  139. for _, entry := range entries {
  140. if !entry.IsRegular() {
  141. continue
  142. }
  143. wikiName, err := models.WikiFilenameToName(entry.Name())
  144. if err != nil {
  145. if models.IsErrWikiInvalidFileName(err) {
  146. continue
  147. }
  148. ctx.ServerError("WikiFilenameToName", err)
  149. return nil, nil
  150. } else if wikiName == "_Sidebar" || wikiName == "_Footer" {
  151. continue
  152. }
  153. pages = append(pages, PageMeta{
  154. Name: wikiName,
  155. SubURL: models.WikiNameToSubURL(wikiName),
  156. })
  157. }
  158. ctx.Data["Pages"] = pages
  159. // get requested pagename
  160. pageName := models.NormalizeWikiName(ctx.Params(":page"))
  161. if len(pageName) == 0 {
  162. pageName = "Home"
  163. }
  164. ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
  165. ctx.Data["old_title"] = pageName
  166. ctx.Data["Title"] = pageName
  167. ctx.Data["title"] = pageName
  168. ctx.Data["RequireHighlightJS"] = true
  169. //lookup filename in wiki - get filecontent, gitTree entry , real filename
  170. data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
  171. if noEntry {
  172. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
  173. }
  174. if entry == nil || ctx.Written() {
  175. return nil, nil
  176. }
  177. sidebarContent, _, _, _ := wikiContentsByName(ctx, commit, "_Sidebar")
  178. if ctx.Written() {
  179. return nil, nil
  180. }
  181. footerContent, _, _, _ := wikiContentsByName(ctx, commit, "_Footer")
  182. if ctx.Written() {
  183. return nil, nil
  184. }
  185. metas := ctx.Repo.Repository.ComposeMetas()
  186. ctx.Data["content"] = markdown.RenderWiki(data, ctx.Repo.RepoLink, metas)
  187. ctx.Data["sidebarPresent"] = sidebarContent != nil
  188. ctx.Data["sidebarContent"] = markdown.RenderWiki(sidebarContent, ctx.Repo.RepoLink, metas)
  189. ctx.Data["footerPresent"] = footerContent != nil
  190. ctx.Data["footerContent"] = markdown.RenderWiki(footerContent, ctx.Repo.RepoLink, metas)
  191. // get commit count - wiki revisions
  192. commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
  193. ctx.Data["CommitCount"] = commitsCount
  194. return wikiRepo, entry
  195. }
  196. func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
  197. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  198. if err != nil {
  199. if !git.IsErrNotExist(err) {
  200. ctx.ServerError("GetBranchCommit", err)
  201. }
  202. return nil, nil
  203. }
  204. // get requested pagename
  205. pageName := models.NormalizeWikiName(ctx.Params(":page"))
  206. if len(pageName) == 0 {
  207. pageName = "Home"
  208. }
  209. ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
  210. ctx.Data["old_title"] = pageName
  211. ctx.Data["Title"] = pageName
  212. ctx.Data["title"] = pageName
  213. ctx.Data["RequireHighlightJS"] = true
  214. //lookup filename in wiki - get filecontent, gitTree entry , real filename
  215. data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
  216. if noEntry {
  217. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
  218. }
  219. if entry == nil || ctx.Written() {
  220. return nil, nil
  221. }
  222. ctx.Data["content"] = string(data)
  223. ctx.Data["sidebarPresent"] = false
  224. ctx.Data["sidebarContent"] = ""
  225. ctx.Data["footerPresent"] = false
  226. ctx.Data["footerContent"] = ""
  227. // get commit count - wiki revisions
  228. commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
  229. ctx.Data["CommitCount"] = commitsCount
  230. // get page
  231. page := ctx.QueryInt("page")
  232. if page <= 1 {
  233. page = 1
  234. }
  235. // get Commit Count
  236. commitsHistory, err := wikiRepo.CommitsByFileAndRangeNoFollow("master", pageFilename, page)
  237. if err != nil {
  238. ctx.ServerError("CommitsByFileAndRangeNoFollow", err)
  239. return nil, nil
  240. }
  241. commitsHistory = models.ValidateCommitsWithEmails(commitsHistory)
  242. commitsHistory = models.ParseCommitsWithSignature(commitsHistory)
  243. ctx.Data["Commits"] = commitsHistory
  244. pager := context.NewPagination(int(commitsCount), git.CommitsRangeSize, page, 5)
  245. pager.SetDefaultParams(ctx)
  246. ctx.Data["Page"] = pager
  247. return wikiRepo, entry
  248. }
  249. func renderEditPage(ctx *context.Context) {
  250. _, commit, err := findWikiRepoCommit(ctx)
  251. if err != nil {
  252. if !git.IsErrNotExist(err) {
  253. ctx.ServerError("GetBranchCommit", err)
  254. }
  255. return
  256. }
  257. // get requested pagename
  258. pageName := models.NormalizeWikiName(ctx.Params(":page"))
  259. if len(pageName) == 0 {
  260. pageName = "Home"
  261. }
  262. ctx.Data["PageURL"] = models.WikiNameToSubURL(pageName)
  263. ctx.Data["old_title"] = pageName
  264. ctx.Data["Title"] = pageName
  265. ctx.Data["title"] = pageName
  266. ctx.Data["RequireHighlightJS"] = true
  267. //lookup filename in wiki - get filecontent, gitTree entry , real filename
  268. data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
  269. if noEntry {
  270. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages")
  271. }
  272. if entry == nil || ctx.Written() {
  273. return
  274. }
  275. ctx.Data["content"] = string(data)
  276. ctx.Data["sidebarPresent"] = false
  277. ctx.Data["sidebarContent"] = ""
  278. ctx.Data["footerPresent"] = false
  279. ctx.Data["footerContent"] = ""
  280. }
  281. // Wiki renders single wiki page
  282. func Wiki(ctx *context.Context) {
  283. ctx.Data["PageIsWiki"] = true
  284. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
  285. if !ctx.Repo.Repository.HasWiki() {
  286. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  287. ctx.HTML(200, tplWikiStart)
  288. return
  289. }
  290. wikiRepo, entry := renderViewPage(ctx)
  291. if ctx.Written() {
  292. return
  293. }
  294. if entry == nil {
  295. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  296. ctx.HTML(200, tplWikiStart)
  297. return
  298. }
  299. wikiPath := entry.Name()
  300. if markup.Type(wikiPath) != markdown.MarkupName {
  301. ext := strings.ToUpper(filepath.Ext(wikiPath))
  302. ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext)
  303. }
  304. // Get last change information.
  305. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  306. if err != nil {
  307. ctx.ServerError("GetCommitByPath", err)
  308. return
  309. }
  310. ctx.Data["Author"] = lastCommit.Author
  311. ctx.HTML(200, tplWikiView)
  312. }
  313. // WikiRevision renders file revision list of wiki page
  314. func WikiRevision(ctx *context.Context) {
  315. ctx.Data["PageIsWiki"] = true
  316. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
  317. if !ctx.Repo.Repository.HasWiki() {
  318. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  319. ctx.HTML(200, tplWikiStart)
  320. return
  321. }
  322. wikiRepo, entry := renderRevisionPage(ctx)
  323. if ctx.Written() {
  324. return
  325. }
  326. if entry == nil {
  327. ctx.Data["Title"] = ctx.Tr("repo.wiki")
  328. ctx.HTML(200, tplWikiStart)
  329. return
  330. }
  331. // Get last change information.
  332. wikiPath := entry.Name()
  333. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath)
  334. if err != nil {
  335. ctx.ServerError("GetCommitByPath", err)
  336. return
  337. }
  338. ctx.Data["Author"] = lastCommit.Author
  339. ctx.HTML(200, tplWikiRevision)
  340. }
  341. // WikiPages render wiki pages list page
  342. func WikiPages(ctx *context.Context) {
  343. if !ctx.Repo.Repository.HasWiki() {
  344. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  345. return
  346. }
  347. ctx.Data["Title"] = ctx.Tr("repo.wiki.pages")
  348. ctx.Data["PageIsWiki"] = true
  349. ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) && !ctx.Repo.Repository.IsArchived
  350. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  351. if err != nil {
  352. return
  353. }
  354. entries, err := commit.ListEntries()
  355. if err != nil {
  356. ctx.ServerError("ListEntries", err)
  357. return
  358. }
  359. pages := make([]PageMeta, 0, len(entries))
  360. for _, entry := range entries {
  361. if !entry.IsRegular() {
  362. continue
  363. }
  364. c, err := wikiRepo.GetCommitByPath(entry.Name())
  365. if err != nil {
  366. ctx.ServerError("GetCommit", err)
  367. return
  368. }
  369. wikiName, err := models.WikiFilenameToName(entry.Name())
  370. if err != nil {
  371. if models.IsErrWikiInvalidFileName(err) {
  372. continue
  373. }
  374. ctx.ServerError("WikiFilenameToName", err)
  375. return
  376. }
  377. pages = append(pages, PageMeta{
  378. Name: wikiName,
  379. SubURL: models.WikiNameToSubURL(wikiName),
  380. UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
  381. })
  382. }
  383. ctx.Data["Pages"] = pages
  384. ctx.HTML(200, tplWikiPages)
  385. }
  386. // WikiRaw outputs raw blob requested by user (image for example)
  387. func WikiRaw(ctx *context.Context) {
  388. wikiRepo, commit, err := findWikiRepoCommit(ctx)
  389. if err != nil {
  390. if wikiRepo != nil {
  391. return
  392. }
  393. }
  394. providedPath := ctx.Params("*")
  395. var entry *git.TreeEntry
  396. if commit != nil {
  397. // Try to find a file with that name
  398. entry, err = findEntryForFile(commit, providedPath)
  399. if err != nil {
  400. ctx.ServerError("findFile", err)
  401. return
  402. }
  403. if entry == nil {
  404. // Try to find a wiki page with that name
  405. if strings.HasSuffix(providedPath, ".md") {
  406. providedPath = providedPath[:len(providedPath)-3]
  407. }
  408. wikiPath := models.WikiNameToFilename(providedPath)
  409. entry, err = findEntryForFile(commit, wikiPath)
  410. if err != nil {
  411. ctx.ServerError("findFile", err)
  412. return
  413. }
  414. }
  415. }
  416. if entry != nil {
  417. if err = ServeBlob(ctx, entry.Blob()); err != nil {
  418. ctx.ServerError("ServeBlob", err)
  419. }
  420. return
  421. }
  422. ctx.NotFound("findEntryForFile", nil)
  423. }
  424. // NewWiki render wiki create page
  425. func NewWiki(ctx *context.Context) {
  426. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  427. ctx.Data["PageIsWiki"] = true
  428. ctx.Data["RequireSimpleMDE"] = true
  429. if !ctx.Repo.Repository.HasWiki() {
  430. ctx.Data["title"] = "Home"
  431. }
  432. ctx.HTML(200, tplWikiNew)
  433. }
  434. // NewWikiPost response for wiki create request
  435. func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  436. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  437. ctx.Data["PageIsWiki"] = true
  438. ctx.Data["RequireSimpleMDE"] = true
  439. if ctx.HasError() {
  440. ctx.HTML(200, tplWikiNew)
  441. return
  442. }
  443. if util.IsEmptyString(form.Title) {
  444. ctx.RenderWithErr(ctx.Tr("repo.issues.new.title_empty"), tplWikiNew, form)
  445. return
  446. }
  447. wikiName := models.NormalizeWikiName(form.Title)
  448. if err := ctx.Repo.Repository.AddWikiPage(ctx.User, wikiName, form.Content, form.Message); err != nil {
  449. if models.IsErrWikiReservedName(err) {
  450. ctx.Data["Err_Title"] = true
  451. ctx.RenderWithErr(ctx.Tr("repo.wiki.reserved_page", wikiName), tplWikiNew, &form)
  452. } else if models.IsErrWikiAlreadyExist(err) {
  453. ctx.Data["Err_Title"] = true
  454. ctx.RenderWithErr(ctx.Tr("repo.wiki.page_already_exists"), tplWikiNew, &form)
  455. } else {
  456. ctx.ServerError("AddWikiPage", err)
  457. }
  458. return
  459. }
  460. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName))
  461. }
  462. // EditWiki render wiki modify page
  463. func EditWiki(ctx *context.Context) {
  464. ctx.Data["PageIsWiki"] = true
  465. ctx.Data["PageIsWikiEdit"] = true
  466. ctx.Data["RequireSimpleMDE"] = true
  467. if !ctx.Repo.Repository.HasWiki() {
  468. ctx.Redirect(ctx.Repo.RepoLink + "/wiki")
  469. return
  470. }
  471. renderEditPage(ctx)
  472. if ctx.Written() {
  473. return
  474. }
  475. ctx.HTML(200, tplWikiNew)
  476. }
  477. // EditWikiPost response for wiki modify request
  478. func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
  479. ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
  480. ctx.Data["PageIsWiki"] = true
  481. ctx.Data["RequireSimpleMDE"] = true
  482. if ctx.HasError() {
  483. ctx.HTML(200, tplWikiNew)
  484. return
  485. }
  486. oldWikiName := models.NormalizeWikiName(ctx.Params(":page"))
  487. newWikiName := models.NormalizeWikiName(form.Title)
  488. if err := ctx.Repo.Repository.EditWikiPage(ctx.User, oldWikiName, newWikiName, form.Content, form.Message); err != nil {
  489. ctx.ServerError("EditWikiPage", err)
  490. return
  491. }
  492. ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName))
  493. }
  494. // DeleteWikiPagePost delete wiki page
  495. func DeleteWikiPagePost(ctx *context.Context) {
  496. wikiName := models.NormalizeWikiName(ctx.Params(":page"))
  497. if len(wikiName) == 0 {
  498. wikiName = "Home"
  499. }
  500. if err := ctx.Repo.Repository.DeleteWikiPage(ctx.User, wikiName); err != nil {
  501. ctx.ServerError("DeleteWikiPage", err)
  502. return
  503. }
  504. ctx.JSON(200, map[string]interface{}{
  505. "redirect": ctx.Repo.RepoLink + "/wiki/",
  506. })
  507. }
上海开阖软件有限公司 沪ICP备12045867号-1