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.

view.go 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Copyright 2014 The Gogs 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. "bytes"
  8. "encoding/base64"
  9. "fmt"
  10. gotemplate "html/template"
  11. "io"
  12. "io/ioutil"
  13. "net/http"
  14. "net/url"
  15. "path"
  16. "strconv"
  17. "strings"
  18. "code.gitea.io/gitea/models"
  19. "code.gitea.io/gitea/modules/base"
  20. "code.gitea.io/gitea/modules/cache"
  21. "code.gitea.io/gitea/modules/charset"
  22. "code.gitea.io/gitea/modules/context"
  23. "code.gitea.io/gitea/modules/git"
  24. "code.gitea.io/gitea/modules/highlight"
  25. "code.gitea.io/gitea/modules/lfs"
  26. "code.gitea.io/gitea/modules/log"
  27. "code.gitea.io/gitea/modules/markup"
  28. "code.gitea.io/gitea/modules/setting"
  29. )
  30. const (
  31. tplRepoEMPTY base.TplName = "repo/empty"
  32. tplRepoHome base.TplName = "repo/home"
  33. tplWatchers base.TplName = "repo/watchers"
  34. tplForks base.TplName = "repo/forks"
  35. tplMigrating base.TplName = "repo/migrate/migrating"
  36. )
  37. type namedBlob struct {
  38. name string
  39. isSymlink bool
  40. blob *git.Blob
  41. }
  42. func linesBytesCount(s []byte) int {
  43. nl := []byte{'\n'}
  44. n := bytes.Count(s, nl)
  45. if len(s) > 0 && !bytes.HasSuffix(s, nl) {
  46. n++
  47. }
  48. return n
  49. }
  50. // FIXME: There has to be a more efficient way of doing this
  51. func getReadmeFileFromPath(commit *git.Commit, treePath string) (*namedBlob, error) {
  52. tree, err := commit.SubTree(treePath)
  53. if err != nil {
  54. return nil, err
  55. }
  56. entries, err := tree.ListEntries()
  57. if err != nil {
  58. return nil, err
  59. }
  60. var readmeFiles [4]*namedBlob
  61. var exts = []string{".md", ".txt", ""} // sorted by priority
  62. for _, entry := range entries {
  63. if entry.IsDir() {
  64. continue
  65. }
  66. for i, ext := range exts {
  67. if markup.IsReadmeFile(entry.Name(), ext) {
  68. if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].name, entry.Blob().Name()) {
  69. name := entry.Name()
  70. isSymlink := entry.IsLink()
  71. target := entry
  72. if isSymlink {
  73. target, err = entry.FollowLinks()
  74. if err != nil && !git.IsErrBadLink(err) {
  75. return nil, err
  76. }
  77. }
  78. if target != nil && (target.IsExecutable() || target.IsRegular()) {
  79. readmeFiles[i] = &namedBlob{
  80. name,
  81. isSymlink,
  82. target.Blob(),
  83. }
  84. }
  85. }
  86. }
  87. }
  88. if markup.IsReadmeFile(entry.Name()) {
  89. if readmeFiles[3] == nil || base.NaturalSortLess(readmeFiles[3].name, entry.Blob().Name()) {
  90. name := entry.Name()
  91. isSymlink := entry.IsLink()
  92. if isSymlink {
  93. entry, err = entry.FollowLinks()
  94. if err != nil && !git.IsErrBadLink(err) {
  95. return nil, err
  96. }
  97. }
  98. if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
  99. readmeFiles[3] = &namedBlob{
  100. name,
  101. isSymlink,
  102. entry.Blob(),
  103. }
  104. }
  105. }
  106. }
  107. }
  108. var readmeFile *namedBlob
  109. for _, f := range readmeFiles {
  110. if f != nil {
  111. readmeFile = f
  112. break
  113. }
  114. }
  115. return readmeFile, nil
  116. }
  117. func renderDirectory(ctx *context.Context, treeLink string) {
  118. tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
  119. if err != nil {
  120. ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
  121. return
  122. }
  123. entries, err := tree.ListEntries()
  124. if err != nil {
  125. ctx.ServerError("ListEntries", err)
  126. return
  127. }
  128. entries.CustomSort(base.NaturalSortLess)
  129. var c *git.LastCommitCache
  130. if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
  131. c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache())
  132. }
  133. var latestCommit *git.Commit
  134. ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, c)
  135. if err != nil {
  136. ctx.ServerError("GetCommitsInfo", err)
  137. return
  138. }
  139. // 3 for the extensions in exts[] in order
  140. // the last one is for a readme that doesn't
  141. // strictly match an extension
  142. var readmeFiles [4]*namedBlob
  143. var docsEntries [3]*git.TreeEntry
  144. var exts = []string{".md", ".txt", ""} // sorted by priority
  145. for _, entry := range entries {
  146. if entry.IsDir() {
  147. lowerName := strings.ToLower(entry.Name())
  148. switch lowerName {
  149. case "docs":
  150. if entry.Name() == "docs" || docsEntries[0] == nil {
  151. docsEntries[0] = entry
  152. }
  153. case ".gitea":
  154. if entry.Name() == ".gitea" || docsEntries[1] == nil {
  155. docsEntries[1] = entry
  156. }
  157. case ".github":
  158. if entry.Name() == ".github" || docsEntries[2] == nil {
  159. docsEntries[2] = entry
  160. }
  161. }
  162. continue
  163. }
  164. for i, ext := range exts {
  165. if markup.IsReadmeFile(entry.Name(), ext) {
  166. log.Debug("%s", entry.Name())
  167. name := entry.Name()
  168. isSymlink := entry.IsLink()
  169. target := entry
  170. if isSymlink {
  171. target, err = entry.FollowLinks()
  172. if err != nil && !git.IsErrBadLink(err) {
  173. ctx.ServerError("FollowLinks", err)
  174. return
  175. }
  176. }
  177. log.Debug("%t", target == nil)
  178. if target != nil && (target.IsExecutable() || target.IsRegular()) {
  179. readmeFiles[i] = &namedBlob{
  180. name,
  181. isSymlink,
  182. target.Blob(),
  183. }
  184. }
  185. }
  186. }
  187. if markup.IsReadmeFile(entry.Name()) {
  188. name := entry.Name()
  189. isSymlink := entry.IsLink()
  190. if isSymlink {
  191. entry, err = entry.FollowLinks()
  192. if err != nil && !git.IsErrBadLink(err) {
  193. ctx.ServerError("FollowLinks", err)
  194. return
  195. }
  196. }
  197. if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
  198. readmeFiles[3] = &namedBlob{
  199. name,
  200. isSymlink,
  201. entry.Blob(),
  202. }
  203. }
  204. }
  205. }
  206. var readmeFile *namedBlob
  207. readmeTreelink := treeLink
  208. for _, f := range readmeFiles {
  209. if f != nil {
  210. readmeFile = f
  211. break
  212. }
  213. }
  214. if ctx.Repo.TreePath == "" && readmeFile == nil {
  215. for _, entry := range docsEntries {
  216. if entry == nil {
  217. continue
  218. }
  219. readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName())
  220. if err != nil {
  221. ctx.ServerError("getReadmeFileFromPath", err)
  222. return
  223. }
  224. if readmeFile != nil {
  225. readmeFile.name = entry.Name() + "/" + readmeFile.name
  226. readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName()
  227. break
  228. }
  229. }
  230. }
  231. if readmeFile != nil {
  232. ctx.Data["RawFileLink"] = ""
  233. ctx.Data["ReadmeInList"] = true
  234. ctx.Data["ReadmeExist"] = true
  235. ctx.Data["FileIsSymlink"] = readmeFile.isSymlink
  236. dataRc, err := readmeFile.blob.DataAsync()
  237. if err != nil {
  238. ctx.ServerError("Data", err)
  239. return
  240. }
  241. defer dataRc.Close()
  242. buf := make([]byte, 1024)
  243. n, _ := dataRc.Read(buf)
  244. buf = buf[:n]
  245. isTextFile := base.IsTextFile(buf)
  246. ctx.Data["FileIsText"] = isTextFile
  247. ctx.Data["FileName"] = readmeFile.name
  248. fileSize := int64(0)
  249. isLFSFile := false
  250. ctx.Data["IsLFSFile"] = false
  251. // FIXME: what happens when README file is an image?
  252. if isTextFile && setting.LFS.StartServer {
  253. meta := lfs.IsPointerFile(&buf)
  254. if meta != nil {
  255. meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
  256. if err != nil && err != models.ErrLFSObjectNotExist {
  257. ctx.ServerError("GetLFSMetaObject", err)
  258. return
  259. }
  260. }
  261. if meta != nil {
  262. ctx.Data["IsLFSFile"] = true
  263. isLFSFile = true
  264. // OK read the lfs object
  265. var err error
  266. dataRc, err = lfs.ReadMetaObject(meta)
  267. if err != nil {
  268. ctx.ServerError("ReadMetaObject", err)
  269. return
  270. }
  271. defer dataRc.Close()
  272. buf = make([]byte, 1024)
  273. n, err = dataRc.Read(buf)
  274. if err != nil {
  275. ctx.ServerError("Data", err)
  276. return
  277. }
  278. buf = buf[:n]
  279. isTextFile = base.IsTextFile(buf)
  280. ctx.Data["IsTextFile"] = isTextFile
  281. fileSize = meta.Size
  282. ctx.Data["FileSize"] = meta.Size
  283. filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name))
  284. ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, filenameBase64)
  285. }
  286. }
  287. if !isLFSFile {
  288. fileSize = readmeFile.blob.Size()
  289. }
  290. if isTextFile {
  291. if fileSize >= setting.UI.MaxDisplayFileSize {
  292. // Pretend that this is a normal text file to display 'This file is too large to be shown'
  293. ctx.Data["IsFileTooLarge"] = true
  294. ctx.Data["IsTextFile"] = true
  295. ctx.Data["FileSize"] = fileSize
  296. } else {
  297. d, _ := ioutil.ReadAll(dataRc)
  298. buf = charset.ToUTF8WithFallback(append(buf, d...))
  299. if markupType := markup.Type(readmeFile.name); markupType != "" {
  300. ctx.Data["IsMarkup"] = true
  301. ctx.Data["MarkupType"] = string(markupType)
  302. ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeDocumentMetas()))
  303. } else {
  304. ctx.Data["IsRenderedHTML"] = true
  305. ctx.Data["FileContent"] = strings.ReplaceAll(
  306. gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`,
  307. )
  308. }
  309. }
  310. }
  311. }
  312. // Show latest commit info of repository in table header,
  313. // or of directory if not in root directory.
  314. ctx.Data["LatestCommit"] = latestCommit
  315. verification := models.ParseCommitWithSignature(latestCommit)
  316. if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
  317. ctx.ServerError("CalculateTrustStatus", err)
  318. return
  319. }
  320. ctx.Data["LatestCommitVerification"] = verification
  321. ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
  322. statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), models.ListOptions{})
  323. if err != nil {
  324. log.Error("GetLatestCommitStatus: %v", err)
  325. }
  326. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
  327. ctx.Data["LatestCommitStatuses"] = statuses
  328. // Check permission to add or upload new file.
  329. if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch {
  330. ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived
  331. ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived
  332. }
  333. ctx.Data["SSHDomain"] = setting.SSH.Domain
  334. }
  335. func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
  336. ctx.Data["IsViewFile"] = true
  337. blob := entry.Blob()
  338. dataRc, err := blob.DataAsync()
  339. if err != nil {
  340. ctx.ServerError("DataAsync", err)
  341. return
  342. }
  343. defer dataRc.Close()
  344. ctx.Data["Title"] = ctx.Data["Title"].(string) + " - " + ctx.Repo.TreePath + " at " + ctx.Repo.BranchName
  345. fileSize := blob.Size()
  346. ctx.Data["FileIsSymlink"] = entry.IsLink()
  347. ctx.Data["FileName"] = blob.Name()
  348. ctx.Data["RawFileLink"] = rawLink + "/" + ctx.Repo.TreePath
  349. buf := make([]byte, 1024)
  350. n, _ := dataRc.Read(buf)
  351. buf = buf[:n]
  352. isTextFile := base.IsTextFile(buf)
  353. isLFSFile := false
  354. isDisplayingSource := ctx.Query("display") == "source"
  355. isDisplayingRendered := !isDisplayingSource
  356. //Check for LFS meta file
  357. if isTextFile && setting.LFS.StartServer {
  358. meta := lfs.IsPointerFile(&buf)
  359. if meta != nil {
  360. meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
  361. if err != nil && err != models.ErrLFSObjectNotExist {
  362. ctx.ServerError("GetLFSMetaObject", err)
  363. return
  364. }
  365. }
  366. if meta != nil {
  367. isLFSFile = true
  368. // OK read the lfs object
  369. var err error
  370. dataRc, err = lfs.ReadMetaObject(meta)
  371. if err != nil {
  372. ctx.ServerError("ReadMetaObject", err)
  373. return
  374. }
  375. defer dataRc.Close()
  376. buf = make([]byte, 1024)
  377. n, err = dataRc.Read(buf)
  378. // Error EOF don't mean there is an error, it just means we read to
  379. // the end
  380. if err != nil && err != io.EOF {
  381. ctx.ServerError("Data", err)
  382. return
  383. }
  384. buf = buf[:n]
  385. isTextFile = base.IsTextFile(buf)
  386. fileSize = meta.Size
  387. ctx.Data["RawFileLink"] = fmt.Sprintf("%s/media/%s/%s", ctx.Repo.RepoLink, ctx.Repo.BranchNameSubURL(), ctx.Repo.TreePath)
  388. }
  389. }
  390. isRepresentableAsText := base.IsRepresentableAsText(buf)
  391. if !isRepresentableAsText {
  392. // If we can't show plain text, always try to render.
  393. isDisplayingSource = false
  394. isDisplayingRendered = true
  395. }
  396. ctx.Data["IsLFSFile"] = isLFSFile
  397. ctx.Data["FileSize"] = fileSize
  398. ctx.Data["IsTextFile"] = isTextFile
  399. ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
  400. ctx.Data["IsDisplayingSource"] = isDisplayingSource
  401. ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
  402. ctx.Data["IsTextSource"] = isTextFile || isDisplayingSource
  403. // Check LFS Lock
  404. lfsLock, err := ctx.Repo.Repository.GetTreePathLock(ctx.Repo.TreePath)
  405. ctx.Data["LFSLock"] = lfsLock
  406. if err != nil {
  407. ctx.ServerError("GetTreePathLock", err)
  408. return
  409. }
  410. if lfsLock != nil {
  411. ctx.Data["LFSLockOwner"] = lfsLock.Owner.DisplayName()
  412. ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
  413. }
  414. // Assume file is not editable first.
  415. if isLFSFile {
  416. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
  417. } else if !isRepresentableAsText {
  418. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
  419. }
  420. switch {
  421. case isRepresentableAsText:
  422. // This will be true for SVGs.
  423. if base.IsImageFile(buf) {
  424. ctx.Data["IsImageFile"] = true
  425. ctx.Data["HasSourceRenderedToggle"] = true
  426. }
  427. if fileSize >= setting.UI.MaxDisplayFileSize {
  428. ctx.Data["IsFileTooLarge"] = true
  429. break
  430. }
  431. d, _ := ioutil.ReadAll(dataRc)
  432. buf = charset.ToUTF8WithFallback(append(buf, d...))
  433. readmeExist := markup.IsReadmeFile(blob.Name())
  434. ctx.Data["ReadmeExist"] = readmeExist
  435. if markupType := markup.Type(blob.Name()); markupType != "" {
  436. ctx.Data["IsMarkup"] = true
  437. ctx.Data["MarkupType"] = markupType
  438. ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeDocumentMetas()))
  439. } else if readmeExist {
  440. ctx.Data["IsRenderedHTML"] = true
  441. ctx.Data["FileContent"] = strings.ReplaceAll(
  442. gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`,
  443. )
  444. } else {
  445. buf = charset.ToUTF8WithFallback(buf)
  446. lineNums := linesBytesCount(buf)
  447. ctx.Data["NumLines"] = strconv.Itoa(lineNums)
  448. ctx.Data["NumLinesSet"] = true
  449. ctx.Data["FileContent"] = highlight.File(lineNums, blob.Name(), buf)
  450. }
  451. if !isLFSFile {
  452. if ctx.Repo.CanEnableEditor() {
  453. if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID {
  454. ctx.Data["CanEditFile"] = false
  455. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
  456. } else {
  457. ctx.Data["CanEditFile"] = true
  458. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
  459. }
  460. } else if !ctx.Repo.IsViewBranch {
  461. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
  462. } else if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  463. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
  464. }
  465. }
  466. case base.IsPDFFile(buf):
  467. ctx.Data["IsPDFFile"] = true
  468. case base.IsVideoFile(buf):
  469. ctx.Data["IsVideoFile"] = true
  470. case base.IsAudioFile(buf):
  471. ctx.Data["IsAudioFile"] = true
  472. case base.IsImageFile(buf):
  473. ctx.Data["IsImageFile"] = true
  474. default:
  475. if fileSize >= setting.UI.MaxDisplayFileSize {
  476. ctx.Data["IsFileTooLarge"] = true
  477. break
  478. }
  479. if markupType := markup.Type(blob.Name()); markupType != "" {
  480. d, _ := ioutil.ReadAll(dataRc)
  481. buf = append(buf, d...)
  482. ctx.Data["IsMarkup"] = true
  483. ctx.Data["MarkupType"] = markupType
  484. ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeDocumentMetas()))
  485. }
  486. }
  487. if ctx.Repo.CanEnableEditor() {
  488. if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID {
  489. ctx.Data["CanDeleteFile"] = false
  490. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
  491. } else {
  492. ctx.Data["CanDeleteFile"] = true
  493. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
  494. }
  495. } else if !ctx.Repo.IsViewBranch {
  496. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
  497. } else if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  498. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
  499. }
  500. }
  501. func safeURL(address string) string {
  502. u, err := url.Parse(address)
  503. if err != nil {
  504. return address
  505. }
  506. u.User = nil
  507. return u.String()
  508. }
  509. // Home render repository home page
  510. func Home(ctx *context.Context) {
  511. if len(ctx.Repo.Units) > 0 {
  512. if ctx.Repo.Repository.IsBeingCreated() {
  513. task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
  514. if err != nil {
  515. ctx.ServerError("models.GetMigratingTask", err)
  516. return
  517. }
  518. cfg, err := task.MigrateConfig()
  519. if err != nil {
  520. ctx.ServerError("task.MigrateConfig", err)
  521. return
  522. }
  523. ctx.Data["Repo"] = ctx.Repo
  524. ctx.Data["MigrateTask"] = task
  525. ctx.Data["CloneAddr"] = safeURL(cfg.CloneAddr)
  526. ctx.HTML(http.StatusOK, tplMigrating)
  527. return
  528. }
  529. if ctx.IsSigned {
  530. // Set repo notification-status read if unread
  531. if err := ctx.Repo.Repository.ReadBy(ctx.User.ID); err != nil {
  532. ctx.ServerError("ReadBy", err)
  533. return
  534. }
  535. }
  536. var firstUnit *models.Unit
  537. for _, repoUnit := range ctx.Repo.Units {
  538. if repoUnit.Type == models.UnitTypeCode {
  539. renderCode(ctx)
  540. return
  541. }
  542. unit, ok := models.Units[repoUnit.Type]
  543. if ok && (firstUnit == nil || !firstUnit.IsLessThan(unit)) {
  544. firstUnit = &unit
  545. }
  546. }
  547. if firstUnit != nil {
  548. ctx.Redirect(fmt.Sprintf("%s/%s%s", setting.AppSubURL, ctx.Repo.Repository.FullName(), firstUnit.URI))
  549. return
  550. }
  551. }
  552. ctx.NotFound("Home", fmt.Errorf(ctx.Tr("units.error.no_unit_allowed_repo")))
  553. }
  554. func renderLanguageStats(ctx *context.Context) {
  555. langs, err := ctx.Repo.Repository.GetTopLanguageStats(5)
  556. if err != nil {
  557. ctx.ServerError("Repo.GetTopLanguageStats", err)
  558. return
  559. }
  560. ctx.Data["LanguageStats"] = langs
  561. }
  562. func renderRepoTopics(ctx *context.Context) {
  563. topics, err := models.FindTopics(&models.FindTopicOptions{
  564. RepoID: ctx.Repo.Repository.ID,
  565. })
  566. if err != nil {
  567. ctx.ServerError("models.FindTopics", err)
  568. return
  569. }
  570. ctx.Data["Topics"] = topics
  571. }
  572. func renderCode(ctx *context.Context) {
  573. ctx.Data["PageIsViewCode"] = true
  574. if ctx.Repo.Repository.IsEmpty {
  575. ctx.HTML(http.StatusOK, tplRepoEMPTY)
  576. return
  577. }
  578. title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
  579. if len(ctx.Repo.Repository.Description) > 0 {
  580. title += ": " + ctx.Repo.Repository.Description
  581. }
  582. ctx.Data["Title"] = title
  583. branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  584. treeLink := branchLink
  585. rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
  586. if len(ctx.Repo.TreePath) > 0 {
  587. treeLink += "/" + ctx.Repo.TreePath
  588. }
  589. // Get Topics of this repo
  590. renderRepoTopics(ctx)
  591. if ctx.Written() {
  592. return
  593. }
  594. // Get current entry user currently looking at.
  595. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
  596. if err != nil {
  597. ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
  598. return
  599. }
  600. renderLanguageStats(ctx)
  601. if ctx.Written() {
  602. return
  603. }
  604. if entry.IsDir() {
  605. renderDirectory(ctx, treeLink)
  606. } else {
  607. renderFile(ctx, entry, treeLink, rawLink)
  608. }
  609. if ctx.Written() {
  610. return
  611. }
  612. var treeNames []string
  613. paths := make([]string, 0, 5)
  614. if len(ctx.Repo.TreePath) > 0 {
  615. treeNames = strings.Split(ctx.Repo.TreePath, "/")
  616. for i := range treeNames {
  617. paths = append(paths, strings.Join(treeNames[:i+1], "/"))
  618. }
  619. ctx.Data["HasParentPath"] = true
  620. if len(paths)-2 >= 0 {
  621. ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
  622. }
  623. }
  624. ctx.Data["Paths"] = paths
  625. ctx.Data["TreeLink"] = treeLink
  626. ctx.Data["TreeNames"] = treeNames
  627. ctx.Data["BranchLink"] = branchLink
  628. ctx.HTML(http.StatusOK, tplRepoHome)
  629. }
  630. // RenderUserCards render a page show users according the input templaet
  631. func RenderUserCards(ctx *context.Context, total int, getter func(opts models.ListOptions) ([]*models.User, error), tpl base.TplName) {
  632. page := ctx.QueryInt("page")
  633. if page <= 0 {
  634. page = 1
  635. }
  636. pager := context.NewPagination(total, models.ItemsPerPage, page, 5)
  637. ctx.Data["Page"] = pager
  638. items, err := getter(models.ListOptions{
  639. Page: pager.Paginater.Current(),
  640. PageSize: models.ItemsPerPage,
  641. })
  642. if err != nil {
  643. ctx.ServerError("getter", err)
  644. return
  645. }
  646. ctx.Data["Cards"] = items
  647. ctx.HTML(http.StatusOK, tpl)
  648. }
  649. // Watchers render repository's watch users
  650. func Watchers(ctx *context.Context) {
  651. ctx.Data["Title"] = ctx.Tr("repo.watchers")
  652. ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers")
  653. ctx.Data["PageIsWatchers"] = true
  654. RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers, tplWatchers)
  655. }
  656. // Stars render repository's starred users
  657. func Stars(ctx *context.Context) {
  658. ctx.Data["Title"] = ctx.Tr("repo.stargazers")
  659. ctx.Data["CardsTitle"] = ctx.Tr("repo.stargazers")
  660. ctx.Data["PageIsStargazers"] = true
  661. RenderUserCards(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers, tplWatchers)
  662. }
  663. // Forks render repository's forked users
  664. func Forks(ctx *context.Context) {
  665. ctx.Data["Title"] = ctx.Tr("repos.forks")
  666. // TODO: need pagination
  667. forks, err := ctx.Repo.Repository.GetForks(models.ListOptions{})
  668. if err != nil {
  669. ctx.ServerError("GetForks", err)
  670. return
  671. }
  672. for _, fork := range forks {
  673. if err = fork.GetOwner(); err != nil {
  674. ctx.ServerError("GetOwner", err)
  675. return
  676. }
  677. }
  678. ctx.Data["Forks"] = forks
  679. ctx.HTML(http.StatusOK, tplForks)
  680. }