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

view.go 28 kB

Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
Improve listing performance by using go-git (#6478) * Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
6 years ago
3 years ago
Better logging (#6038) (#6095) * Panic don't fatal on create new logger Fixes #5854 Signed-off-by: Andrew Thornton <art27@cantab.net> * partial broken * Update the logging infrastrcture Signed-off-by: Andrew Thornton <art27@cantab.net> * Reset the skip levels for Fatal and Error Signed-off-by: Andrew Thornton <art27@cantab.net> * broken ncsa * More log.Error fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove nal * set log-levels to lowercase * Make console_test test all levels * switch to lowercased levels * OK now working * Fix vetting issues * Fix lint * Fix tests * change default logging to match current gitea * Improve log testing Signed-off-by: Andrew Thornton <art27@cantab.net> * reset error skip levels to 0 * Update documentation and access logger configuration * Redirect the router log back to gitea if redirect macaron log but also allow setting the log level - i.e. TRACE * Fix broken level caching * Refactor the router log * Add Router logger * Add colorizing options * Adjust router colors * Only create logger if they will be used * update app.ini.sample * rename Attribute ColorAttribute * Change from white to green for function * Set fatal/error levels * Restore initial trace logger * Fix Trace arguments in modules/auth/auth.go * Properly handle XORMLogger * Improve admin/config page * fix fmt * Add auto-compression of old logs * Update error log levels * Remove the unnecessary skip argument from Error, Fatal and Critical * Add stacktrace support * Fix tests * Remove x/sync from vendors? * Add stderr option to console logger * Use filepath.ToSlash to protect against Windows in tests * Remove prefixed underscores from names in colors.go * Remove not implemented database logger This was removed from Gogs on 4 Mar 2016 but left in the configuration since then. * Ensure that log paths are relative to ROOT_PATH * use path.Join * rename jsonConfig to logConfig * Rename "config" to "jsonConfig" to make it clearer * Requested changes * Requested changes: XormLogger * Try to color the windows terminal If successful default to colorizing the console logs * fixup * Colorize initially too * update vendor * Colorize logs on default and remove if this is not a colorizing logger * Fix documentation * fix test * Use go-isatty to detect if on windows we are on msys or cygwin * Fix spelling mistake * Add missing vendors * More changes * Rationalise the ANSI writer protection * Adjust colors on advice from @0x5c * Make Flags a comma separated list * Move to use the windows constant for ENABLE_VIRTUAL_TERMINAL_PROCESSING * Ensure matching is done on the non-colored message - to simpify EXPRESSION
6 years ago
10 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
Git LFS support v2 (#122) * Import github.com/git-lfs/lfs-test-server as lfs module base Imported commit is 3968aac269a77b73924649b9412ae03f7ccd3198 Removed: Dockerfile CONTRIBUTING.md mgmt* script/ vendor/ kvlogger.go .dockerignore .gitignore README.md * Remove config, add JWT support from github.com/mgit-at/lfs-test-server Imported commit f0cdcc5a01599c5a955dc1bbf683bb4acecdba83 * Add LFS settings * Add LFS meta object model * Add LFS routes and initialization * Import github.com/dgrijalva/jwt-go into vendor/ * Adapt LFS module: handlers, routing, meta store * Move LFS routes to /user/repo/info/lfs/* * Add request header checks to LFS BatchHandler / PostHandler * Implement LFS basic authentication * Rework JWT secret generation / load * Implement LFS SSH token authentication with JWT Specification: https://github.com/github/git-lfs/tree/master/docs/api * Integrate LFS settings into install process * Remove LFS objects when repository is deleted Only removes objects from content store when deleted repo is the only referencing repository * Make LFS module stateless Fixes bug where LFS would not work after installation without restarting Gitea * Change 500 'Internal Server Error' to 400 'Bad Request' * Change sql query to xorm call * Remove unneeded type from LFS module * Change internal imports to code.gitea.io/gitea/ * Add Gitea authors copyright * Change basic auth realm to "gitea-lfs" * Add unique indexes to LFS model * Use xorm count function in LFS check on repository delete * Return io.ReadCloser from content store and close after usage * Add LFS info to runWeb() * Export LFS content store base path * LFS file download from UI * Work around git-lfs client issue with unauthenticated requests Returning a dummy Authorization header for unauthenticated requests lets git-lfs client skip asking for auth credentials See: https://github.com/github/git-lfs/issues/1088 * Fix unauthenticated UI downloads from public repositories * Authentication check order, Finish LFS file view logic * Ignore LFS hooks if installed for current OS user Fixes Gitea UI actions for repositories tracking LFS files. Checks for minimum needed git version by parsing the semantic version string. * Hide LFS metafile diff from commit view, marking as binary * Show LFS notice if file in commit view is tracked * Add notbefore/nbf JWT claim * Correct lint suggestions - comments for structs and functions - Add comments to LFS model - Function comment for GetRandomBytesAsBase64 - LFS server function comments and lint variable suggestion * Move secret generation code out of conditional Ensures no LFS code may run with an empty secret * Do not hand out JWT tokens if LFS server support is disabled
8 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
9 years ago
9 years ago
API add/generalize pagination (#9452) * paginate results * fixed deadlock * prevented breaking change * updated swagger * go fmt * fixed find topic * go mod tidy * go mod vendor with go1.13.5 * fixed repo find topics * fixed unit test * added Limit method to Engine struct; use engine variable when provided; fixed gitignore * use ItemsPerPage for default pagesize; fix GetWatchers, getOrgUsersByOrgID and GetStargazers; fix GetAllCommits headers; reverted some changed behaviors * set Page value on Home route * improved memory allocations * fixed response headers * removed logfiles * fixed import order * import order * improved swagger * added function to get models.ListOptions from context * removed pagesize diff on unit test * fixed imports * removed unnecessary struct field * fixed go fmt * scoped PR * code improvements * code improvements * go mod tidy * fixed import order * fixed commit statuses session * fixed files headers * fixed headers; added pagination for notifications * go mod tidy * go fmt * removed Private from user search options; added setting.UI.IssuePagingNum as default valeu on repo's issues list * Apply suggestions from code review Co-Authored-By: 6543 <6543@obermui.de> Co-Authored-By: zeripath <art27@cantab.net> * fixed build error * CI.restart() * fixed merge conflicts resolve * fixed conflicts resolve * improved FindTrackedTimesOptions.ToOptions() method * added backwards compatibility on ListReleases request; fixed issue tracked time ToSession * fixed build error; fixed swagger template * fixed swagger template * fixed ListReleases backwards compatibility * added page to user search route Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: zeripath <art27@cantab.net>
5 years ago
9 years ago
9 years ago
9 years ago
9 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  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. "code.gitea.io/gitea/modules/options"
  9. "encoding/base64"
  10. "fmt"
  11. gotemplate "html/template"
  12. "io/ioutil"
  13. "net/http"
  14. "net/url"
  15. "path"
  16. "strings"
  17. "time"
  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/migrating"
  36. tplContributors base.TplName = "repo/contributors"
  37. )
  38. type namedBlob struct {
  39. name string
  40. isSymlink bool
  41. blob *git.Blob
  42. }
  43. // FIXME: There has to be a more efficient way of doing this
  44. func getReadmeFileFromPath(commit *git.Commit, treePath string) (*namedBlob, error) {
  45. tree, err := commit.SubTree(treePath)
  46. if err != nil {
  47. return nil, err
  48. }
  49. entries, err := tree.ListEntries()
  50. if err != nil {
  51. return nil, err
  52. }
  53. var readmeFiles [4]*namedBlob
  54. var exts = []string{".md", ".txt", ""} // sorted by priority
  55. for _, entry := range entries {
  56. if entry.IsDir() {
  57. continue
  58. }
  59. for i, ext := range exts {
  60. if markup.IsReadmeFile(entry.Name(), ext) {
  61. if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].name, entry.Blob().Name()) {
  62. name := entry.Name()
  63. isSymlink := entry.IsLink()
  64. target := entry
  65. if isSymlink {
  66. target, err = entry.FollowLinks()
  67. if err != nil && !git.IsErrBadLink(err) {
  68. return nil, err
  69. }
  70. }
  71. if target != nil && (target.IsExecutable() || target.IsRegular()) {
  72. readmeFiles[i] = &namedBlob{
  73. name,
  74. isSymlink,
  75. target.Blob(),
  76. }
  77. }
  78. }
  79. }
  80. }
  81. if markup.IsReadmeFile(entry.Name()) {
  82. if readmeFiles[3] == nil || base.NaturalSortLess(readmeFiles[3].name, entry.Blob().Name()) {
  83. name := entry.Name()
  84. isSymlink := entry.IsLink()
  85. if isSymlink {
  86. entry, err = entry.FollowLinks()
  87. if err != nil && !git.IsErrBadLink(err) {
  88. return nil, err
  89. }
  90. }
  91. if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
  92. readmeFiles[3] = &namedBlob{
  93. name,
  94. isSymlink,
  95. entry.Blob(),
  96. }
  97. }
  98. }
  99. }
  100. }
  101. var readmeFile *namedBlob
  102. for _, f := range readmeFiles {
  103. if f != nil {
  104. readmeFile = f
  105. break
  106. }
  107. }
  108. return readmeFile, nil
  109. }
  110. func renderDirectory(ctx *context.Context, treeLink string) {
  111. tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
  112. if err != nil {
  113. ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
  114. return
  115. }
  116. entries, err := tree.ListEntries()
  117. if err != nil {
  118. ctx.ServerError("ListEntries", err)
  119. return
  120. }
  121. entries.CustomSort(base.NaturalSortLess)
  122. var c git.LastCommitCache
  123. if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
  124. c = cache.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, int64(setting.CacheService.LastCommit.TTL.Seconds()))
  125. }
  126. var latestCommit *git.Commit
  127. ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath, c)
  128. if err != nil {
  129. ctx.ServerError("GetCommitsInfo", err)
  130. return
  131. }
  132. // 3 for the extensions in exts[] in order
  133. // the last one is for a readme that doesn't
  134. // strictly match an extension
  135. var readmeFiles [4]*namedBlob
  136. var docsEntries [3]*git.TreeEntry
  137. var exts = []string{".md", ".txt", ""} // sorted by priority
  138. for _, entry := range entries {
  139. if entry.IsDir() {
  140. lowerName := strings.ToLower(entry.Name())
  141. switch lowerName {
  142. case "docs":
  143. if entry.Name() == "docs" || docsEntries[0] == nil {
  144. docsEntries[0] = entry
  145. }
  146. case ".gitea":
  147. if entry.Name() == ".gitea" || docsEntries[1] == nil {
  148. docsEntries[1] = entry
  149. }
  150. case ".github":
  151. if entry.Name() == ".github" || docsEntries[2] == nil {
  152. docsEntries[2] = entry
  153. }
  154. }
  155. continue
  156. }
  157. for i, ext := range exts {
  158. if markup.IsReadmeFile(entry.Name(), ext) {
  159. log.Debug("%s", entry.Name())
  160. name := entry.Name()
  161. isSymlink := entry.IsLink()
  162. target := entry
  163. if isSymlink {
  164. target, err = entry.FollowLinks()
  165. if err != nil && !git.IsErrBadLink(err) {
  166. ctx.ServerError("FollowLinks", err)
  167. return
  168. }
  169. }
  170. log.Debug("%t", target == nil)
  171. if target != nil && (target.IsExecutable() || target.IsRegular()) {
  172. readmeFiles[i] = &namedBlob{
  173. name,
  174. isSymlink,
  175. target.Blob(),
  176. }
  177. }
  178. }
  179. }
  180. if markup.IsReadmeFile(entry.Name()) {
  181. name := entry.Name()
  182. isSymlink := entry.IsLink()
  183. if isSymlink {
  184. entry, err = entry.FollowLinks()
  185. if err != nil && !git.IsErrBadLink(err) {
  186. ctx.ServerError("FollowLinks", err)
  187. return
  188. }
  189. }
  190. if entry != nil && (entry.IsExecutable() || entry.IsRegular()) {
  191. readmeFiles[3] = &namedBlob{
  192. name,
  193. isSymlink,
  194. entry.Blob(),
  195. }
  196. }
  197. }
  198. }
  199. var readmeFile *namedBlob
  200. readmeTreelink := treeLink
  201. for _, f := range readmeFiles {
  202. if f != nil {
  203. readmeFile = f
  204. break
  205. }
  206. }
  207. if ctx.Repo.TreePath == "" && readmeFile == nil {
  208. for _, entry := range docsEntries {
  209. if entry == nil {
  210. continue
  211. }
  212. readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName())
  213. if err != nil {
  214. ctx.ServerError("getReadmeFileFromPath", err)
  215. return
  216. }
  217. if readmeFile != nil {
  218. readmeFile.name = entry.Name() + "/" + readmeFile.name
  219. readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName()
  220. break
  221. }
  222. }
  223. }
  224. if readmeFile != nil {
  225. ctx.Data["RawFileLink"] = ""
  226. ctx.Data["ReadmeInList"] = true
  227. ctx.Data["ReadmeExist"] = true
  228. ctx.Data["FileIsSymlink"] = readmeFile.isSymlink
  229. ctx.Data["ReadmeName"] = readmeFile.name
  230. if ctx.Repo.CanEnableEditor() {
  231. ctx.Data["CanEditFile"] = true
  232. }
  233. dataRc, err := readmeFile.blob.DataAsync()
  234. if err != nil {
  235. ctx.ServerError("Data", err)
  236. return
  237. }
  238. defer dataRc.Close()
  239. buf := make([]byte, 1024)
  240. n, _ := dataRc.Read(buf)
  241. buf = buf[:n]
  242. isTextFile := base.IsTextFile(buf)
  243. ctx.Data["FileIsText"] = isTextFile
  244. ctx.Data["FileName"] = readmeFile.name
  245. fileSize := int64(0)
  246. isLFSFile := false
  247. ctx.Data["IsLFSFile"] = false
  248. // FIXME: what happens when README file is an image?
  249. if isTextFile && setting.LFS.StartServer {
  250. meta := lfs.IsPointerFile(&buf)
  251. if meta != nil {
  252. meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
  253. if err != nil && err != models.ErrLFSObjectNotExist {
  254. ctx.ServerError("GetLFSMetaObject", err)
  255. return
  256. }
  257. }
  258. if meta != nil {
  259. ctx.Data["IsLFSFile"] = true
  260. isLFSFile = true
  261. // OK read the lfs object
  262. var err error
  263. dataRc, err = lfs.ReadMetaObject(meta)
  264. if err != nil {
  265. ctx.ServerError("ReadMetaObject", err)
  266. return
  267. }
  268. defer dataRc.Close()
  269. buf = make([]byte, 1024)
  270. n, err = dataRc.Read(buf)
  271. if err != nil {
  272. ctx.ServerError("Data", err)
  273. return
  274. }
  275. buf = buf[:n]
  276. isTextFile = base.IsTextFile(buf)
  277. ctx.Data["IsTextFile"] = isTextFile
  278. fileSize = meta.Size
  279. ctx.Data["FileSize"] = meta.Size
  280. filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name))
  281. ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, filenameBase64)
  282. }
  283. }
  284. if !isLFSFile {
  285. fileSize = readmeFile.blob.Size()
  286. }
  287. if isTextFile {
  288. if fileSize >= setting.UI.MaxDisplayFileSize {
  289. // Pretend that this is a normal text file to display 'This file is too large to be shown'
  290. ctx.Data["IsFileTooLarge"] = true
  291. ctx.Data["IsTextFile"] = true
  292. ctx.Data["FileSize"] = fileSize
  293. } else {
  294. d, _ := ioutil.ReadAll(dataRc)
  295. buf = charset.ToUTF8WithFallback(append(buf, d...))
  296. if markupType := markup.Type(readmeFile.name); markupType != "" {
  297. ctx.Data["IsMarkup"] = true
  298. ctx.Data["MarkupType"] = string(markupType)
  299. ctx.Data["FileContent"] = string(markup.Render(readmeFile.name, buf, readmeTreelink, ctx.Repo.Repository.ComposeMetas()))
  300. } else {
  301. ctx.Data["IsRenderedHTML"] = true
  302. ctx.Data["FileContent"] = strings.Replace(
  303. gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`, -1,
  304. )
  305. }
  306. }
  307. }
  308. }
  309. // Show latest commit info of repository in table header,
  310. // or of directory if not in root directory.
  311. ctx.Data["LatestCommit"] = latestCommit
  312. verification := models.ParseCommitWithSignature(latestCommit)
  313. if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
  314. ctx.ServerError("CalculateTrustStatus", err)
  315. return
  316. }
  317. ctx.Data["LatestCommitVerification"] = verification
  318. ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
  319. statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), 0)
  320. if err != nil {
  321. log.Error("GetLatestCommitStatus: %v", err)
  322. }
  323. ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
  324. // Check permission to add or upload new file.
  325. if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch {
  326. ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived
  327. ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived
  328. }
  329. }
  330. func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
  331. ctx.Data["IsViewFile"] = true
  332. blob := entry.Blob()
  333. dataRc, err := blob.DataAsync()
  334. if err != nil {
  335. ctx.ServerError("DataAsync", err)
  336. return
  337. }
  338. defer dataRc.Close()
  339. ctx.Data["Title"] = ctx.Data["Title"].(string) + " - " + ctx.Repo.TreePath + " at " + ctx.Repo.BranchName
  340. fileSize := blob.Size()
  341. ctx.Data["FileIsSymlink"] = entry.IsLink()
  342. ctx.Data["FileSize"] = fileSize
  343. ctx.Data["FileName"] = blob.Name()
  344. ctx.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())
  345. ctx.Data["RawFileLink"] = rawLink + "/" + ctx.Repo.TreePath
  346. buf := make([]byte, 1024)
  347. n, _ := dataRc.Read(buf)
  348. buf = buf[:n]
  349. isTextFile := base.IsTextFile(buf)
  350. isLFSFile := false
  351. ctx.Data["IsTextFile"] = isTextFile
  352. //Check for LFS meta file
  353. if isTextFile && setting.LFS.StartServer {
  354. meta := lfs.IsPointerFile(&buf)
  355. if meta != nil {
  356. meta, err = ctx.Repo.Repository.GetLFSMetaObjectByOid(meta.Oid)
  357. if err != nil && err != models.ErrLFSObjectNotExist {
  358. ctx.ServerError("GetLFSMetaObject", err)
  359. return
  360. }
  361. }
  362. if meta != nil {
  363. ctx.Data["IsLFSFile"] = true
  364. isLFSFile = true
  365. // OK read the lfs object
  366. var err error
  367. dataRc, err = lfs.ReadMetaObject(meta)
  368. if err != nil {
  369. ctx.ServerError("ReadMetaObject", err)
  370. return
  371. }
  372. defer dataRc.Close()
  373. buf = make([]byte, 1024)
  374. n, err = dataRc.Read(buf)
  375. if err != nil {
  376. ctx.ServerError("Data", err)
  377. return
  378. }
  379. buf = buf[:n]
  380. isTextFile = base.IsTextFile(buf)
  381. ctx.Data["IsTextFile"] = isTextFile
  382. fileSize = meta.Size
  383. ctx.Data["FileSize"] = meta.Size
  384. filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(blob.Name()))
  385. ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, filenameBase64)
  386. }
  387. }
  388. // Check LFS Lock
  389. lfsLock, err := ctx.Repo.Repository.GetTreePathLock(ctx.Repo.TreePath)
  390. ctx.Data["LFSLock"] = lfsLock
  391. if err != nil {
  392. ctx.ServerError("GetTreePathLock", err)
  393. return
  394. }
  395. if lfsLock != nil {
  396. ctx.Data["LFSLockOwner"] = lfsLock.Owner.DisplayName()
  397. ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
  398. }
  399. // Assume file is not editable first.
  400. if isLFSFile {
  401. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
  402. } else if !isTextFile {
  403. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
  404. }
  405. switch {
  406. case isTextFile:
  407. if fileSize >= setting.UI.MaxDisplayFileSize {
  408. ctx.Data["IsFileTooLarge"] = true
  409. break
  410. }
  411. d, _ := ioutil.ReadAll(dataRc)
  412. buf = charset.ToUTF8WithFallback(append(buf, d...))
  413. readmeExist := markup.IsReadmeFile(blob.Name())
  414. ctx.Data["ReadmeExist"] = readmeExist
  415. if markupType := markup.Type(blob.Name()); markupType != "" {
  416. ctx.Data["IsMarkup"] = true
  417. ctx.Data["MarkupType"] = markupType
  418. ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
  419. } else if readmeExist {
  420. ctx.Data["IsRenderedHTML"] = true
  421. ctx.Data["FileContent"] = strings.Replace(
  422. gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`, -1,
  423. )
  424. } else {
  425. // Building code view blocks with line number on server side.
  426. var fileContent string
  427. if content, err := charset.ToUTF8WithErr(buf); err != nil {
  428. log.Error("ToUTF8WithErr: %v", err)
  429. fileContent = string(buf)
  430. } else {
  431. fileContent = content
  432. }
  433. var output bytes.Buffer
  434. lines := strings.Split(fileContent, "\n")
  435. ctx.Data["NumLines"] = len(lines)
  436. if len(lines) == 1 && lines[0] == "" {
  437. // If the file is completely empty, we show zero lines at the line counter
  438. ctx.Data["NumLines"] = 0
  439. }
  440. ctx.Data["NumLinesSet"] = true
  441. //Remove blank line at the end of file
  442. if len(lines) > 0 && lines[len(lines)-1] == "" {
  443. lines = lines[:len(lines)-1]
  444. }
  445. for index, line := range lines {
  446. line = gotemplate.HTMLEscapeString(line)
  447. if index != len(lines)-1 {
  448. line += "\n"
  449. }
  450. output.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, index+1, index+1, line))
  451. }
  452. ctx.Data["FileContent"] = gotemplate.HTML(output.String())
  453. output.Reset()
  454. for i := 0; i < len(lines); i++ {
  455. output.WriteString(fmt.Sprintf(`<span id="L%[1]d" data-line-number="%[1]d"></span>`, i+1))
  456. }
  457. ctx.Data["LineNums"] = gotemplate.HTML(output.String())
  458. }
  459. if !isLFSFile {
  460. if ctx.Repo.CanEnableEditor() {
  461. if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID {
  462. ctx.Data["CanEditFile"] = false
  463. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
  464. } else {
  465. ctx.Data["CanEditFile"] = true
  466. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
  467. }
  468. } else if !ctx.Repo.IsViewBranch {
  469. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
  470. } else if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  471. ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
  472. }
  473. }
  474. case base.IsPDFFile(buf):
  475. ctx.Data["IsPDFFile"] = true
  476. case base.IsVideoFile(buf):
  477. ctx.Data["IsVideoFile"] = true
  478. case base.IsAudioFile(buf):
  479. ctx.Data["IsAudioFile"] = true
  480. case base.IsImageFile(buf):
  481. ctx.Data["IsImageFile"] = true
  482. default:
  483. if fileSize >= setting.UI.MaxDisplayFileSize {
  484. ctx.Data["IsFileTooLarge"] = true
  485. break
  486. }
  487. if markupType := markup.Type(blob.Name()); markupType != "" {
  488. d, _ := ioutil.ReadAll(dataRc)
  489. buf = append(buf, d...)
  490. ctx.Data["IsMarkup"] = true
  491. ctx.Data["MarkupType"] = markupType
  492. ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
  493. }
  494. }
  495. if ctx.Repo.CanEnableEditor() {
  496. if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID {
  497. ctx.Data["CanDeleteFile"] = false
  498. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
  499. } else {
  500. ctx.Data["CanDeleteFile"] = true
  501. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
  502. }
  503. } else if !ctx.Repo.IsViewBranch {
  504. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
  505. } else if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  506. ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
  507. }
  508. }
  509. func safeURL(address string) string {
  510. u, err := url.Parse(address)
  511. if err != nil {
  512. return address
  513. }
  514. u.User = nil
  515. return u.String()
  516. }
  517. type ContributorInfo struct {
  518. UserInfo *models.User // nil for contributor who is not a registered user
  519. RelAvatarLink string `json:"rel_avatar_link"`
  520. UserName string `json:"user_name"`
  521. Email string `json:"email"`
  522. CommitCnt int `json:"commit_cnt"`
  523. }
  524. type GetContributorsInfo struct {
  525. ErrorCode int `json:"error_code"`
  526. ErrorMsg string `json:"error_msg"`
  527. Count int `json:"count"`
  528. ContributorInfo []*ContributorInfo `json:"contributor_info"`
  529. }
  530. func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo {
  531. for _, c := range contributorInfos {
  532. if strings.Compare(c.Email, email) == 0 {
  533. return c
  534. }
  535. }
  536. return nil
  537. }
  538. // Home render repository home page
  539. func Home(ctx *context.Context) {
  540. if len(ctx.Repo.Units) > 0 {
  541. //get repo contributors info
  542. contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath())
  543. if err == nil && contributors != nil {
  544. startTime := time.Now()
  545. var contributorInfos []*ContributorInfo
  546. contributorInfoHash := make(map[string]*ContributorInfo)
  547. count := 0
  548. for _, c := range contributors {
  549. if count >= 25 {
  550. continue
  551. }
  552. if strings.Compare(c.Email, "") == 0 {
  553. continue
  554. }
  555. // get user info from committer email
  556. user, err := models.GetUserByActivateEmail(c.Email)
  557. if err == nil {
  558. // committer is system user, get info through user's primary email
  559. if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
  560. // existed: same primary email, different committer name
  561. existedContributorInfo.CommitCnt += c.CommitCnt
  562. } else {
  563. // new committer info
  564. var newContributor = &ContributorInfo{
  565. user, user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt,
  566. }
  567. count++
  568. contributorInfos = append(contributorInfos, newContributor)
  569. contributorInfoHash[user.Email] = newContributor
  570. }
  571. } else {
  572. // committer is not system user
  573. if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
  574. // existed: same primary email, different committer name
  575. existedContributorInfo.CommitCnt += c.CommitCnt
  576. } else {
  577. var newContributor = &ContributorInfo{
  578. user, "", "",c.Email, c.CommitCnt,
  579. }
  580. count++
  581. contributorInfos = append(contributorInfos, newContributor)
  582. contributorInfoHash[c.Email] = newContributor
  583. }
  584. }
  585. }
  586. ctx.Data["ContributorInfo"] = contributorInfos
  587. var duration = time.Since(startTime)
  588. log.Info("getContributorInfo cost: %v seconds", duration.Seconds())
  589. }
  590. if ctx.Repo.Repository.IsBeingCreated() {
  591. task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
  592. if err != nil {
  593. ctx.ServerError("models.GetMigratingTask", err)
  594. return
  595. }
  596. cfg, err := task.MigrateConfig()
  597. if err != nil {
  598. ctx.ServerError("task.MigrateConfig", err)
  599. return
  600. }
  601. ctx.Data["Repo"] = ctx.Repo
  602. ctx.Data["MigrateTask"] = task
  603. ctx.Data["CloneAddr"] = safeURL(cfg.CloneAddr)
  604. ctx.HTML(200, tplMigrating)
  605. return
  606. }
  607. var firstUnit *models.Unit
  608. for _, repoUnit := range ctx.Repo.Units {
  609. if repoUnit.Type == models.UnitTypeCode {
  610. renderCode(ctx)
  611. return
  612. }
  613. unit, ok := models.Units[repoUnit.Type]
  614. if ok && (firstUnit == nil || !firstUnit.IsLessThan(unit)) {
  615. firstUnit = &unit
  616. }
  617. }
  618. if firstUnit != nil {
  619. ctx.Redirect(fmt.Sprintf("%s/%s%s", setting.AppSubURL, ctx.Repo.Repository.FullName(), firstUnit.URI))
  620. return
  621. }
  622. }
  623. ctx.NotFound("Home", fmt.Errorf(ctx.Tr("units.error.no_unit_allowed_repo")))
  624. }
  625. func renderLicense(ctx *context.Context) {
  626. entry, err := ctx.Repo.Commit.GetTreeEntryByPath("LICENSE")
  627. if err != nil {
  628. log.Error(err.Error())
  629. return
  630. }
  631. blob := entry.Blob()
  632. dataRc, err := blob.DataAsync()
  633. if err != nil {
  634. log.Error("DataAsync", err)
  635. return
  636. }
  637. defer dataRc.Close()
  638. buf, err := ioutil.ReadAll(dataRc)
  639. if err != nil {
  640. log.Error("DataAsync", err)
  641. return
  642. }
  643. for _, f := range models.Licenses {
  644. license, err := options.License(f)
  645. if err != nil {
  646. log.Error("failed to get license content: %v, err:%v", f, err)
  647. continue
  648. }
  649. if bytes.Compare(buf, license) == 0 {
  650. log.Info("got matched license:%v", f)
  651. ctx.Data["LICENSE"] = f
  652. return
  653. }
  654. }
  655. log.Info("not found matched license,repo:%v", ctx.Repo.Repository.Name)
  656. }
  657. func renderLanguageStats(ctx *context.Context) {
  658. langs, err := ctx.Repo.Repository.GetTopLanguageStats(5)
  659. if err != nil {
  660. ctx.ServerError("Repo.GetTopLanguageStats", err)
  661. return
  662. }
  663. ctx.Data["LanguageStats"] = langs
  664. }
  665. func renderRepoTopics(ctx *context.Context) {
  666. topics, err := models.FindTopics(&models.FindTopicOptions{
  667. RepoID: ctx.Repo.Repository.ID,
  668. })
  669. if err != nil {
  670. ctx.ServerError("models.FindTopics", err)
  671. return
  672. }
  673. ctx.Data["Topics"] = topics
  674. }
  675. func renderCode(ctx *context.Context) {
  676. ctx.Data["PageIsViewCode"] = true
  677. if ctx.Repo.Repository.IsEmpty {
  678. ctx.HTML(200, tplRepoEMPTY)
  679. return
  680. }
  681. title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
  682. if len(ctx.Repo.Repository.Description) > 0 {
  683. title += ": " + ctx.Repo.Repository.Description
  684. }
  685. ctx.Data["Title"] = title
  686. ctx.Data["RequireHighlightJS"] = true
  687. branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
  688. treeLink := branchLink
  689. rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
  690. if len(ctx.Repo.TreePath) > 0 {
  691. treeLink += "/" + ctx.Repo.TreePath
  692. }
  693. // Get Topics of this repo
  694. renderRepoTopics(ctx)
  695. // Get license of this repo
  696. renderLicense(ctx)
  697. if ctx.Written() {
  698. return
  699. }
  700. // Get current entry user currently looking at.
  701. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
  702. if err != nil {
  703. ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
  704. return
  705. }
  706. renderLanguageStats(ctx)
  707. if ctx.Written() {
  708. return
  709. }
  710. if entry.IsDir() {
  711. renderDirectory(ctx, treeLink)
  712. } else {
  713. renderFile(ctx, entry, treeLink, rawLink)
  714. }
  715. if ctx.Written() {
  716. return
  717. }
  718. var treeNames []string
  719. paths := make([]string, 0, 5)
  720. if len(ctx.Repo.TreePath) > 0 {
  721. treeNames = strings.Split(ctx.Repo.TreePath, "/")
  722. for i := range treeNames {
  723. paths = append(paths, strings.Join(treeNames[:i+1], "/"))
  724. }
  725. ctx.Data["HasParentPath"] = true
  726. if len(paths)-2 >= 0 {
  727. ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
  728. }
  729. }
  730. //如果是fork的仓库
  731. if ctx.Repo.Repository.IsFork {
  732. //获得fetchUpstream对应的分支参数
  733. /*
  734. // 1. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headBranch}
  735. // 2. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}:{:headBranch}
  736. // 3. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}/{:headRepoName}:{:headBranch}
  737. */
  738. baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath())
  739. defer baseGitRepo.Close()
  740. if err != nil {
  741. log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath())
  742. ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
  743. } else {
  744. if _, error := baseGitRepo.GetBranch(ctx.Repo.BranchName); error == nil {
  745. //base repo has the same branch, then compare between current repo branch and base repo's branch
  746. compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName
  747. ctx.SetParams("*", compareUrl)
  748. ctx.Data["UpstreamSameBranchName"] = true
  749. } else {
  750. //else, compare between current repo branch and base repo's default branch
  751. compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch
  752. ctx.SetParams("*", compareUrl)
  753. ctx.Data["UpstreamSameBranchName"] = false
  754. }
  755. _, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx)
  756. defer headGitRepo.Close()
  757. if compareInfo != nil {
  758. if compareInfo.Commits != nil {
  759. log.Info("compareInfoCommits数量:%d", compareInfo.Commits.Len())
  760. ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len()
  761. } else {
  762. log.Info("compareInfo nothing different")
  763. ctx.Data["FetchUpstreamCnt"] = 0
  764. }
  765. } else {
  766. ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error
  767. }
  768. }
  769. }
  770. ctx.Data["Paths"] = paths
  771. ctx.Data["TreeLink"] = treeLink
  772. ctx.Data["TreeNames"] = treeNames
  773. ctx.Data["BranchLink"] = branchLink
  774. ctx.HTML(200, tplRepoHome)
  775. }
  776. // RenderUserCards render a page show users according the input templaet
  777. func RenderUserCards(ctx *context.Context, total int, getter func(opts models.ListOptions) ([]*models.User, error), tpl base.TplName) {
  778. page := ctx.QueryInt("page")
  779. if page <= 0 {
  780. page = 1
  781. }
  782. pager := context.NewPagination(total, models.ItemsPerPage, page, 5)
  783. ctx.Data["Page"] = pager
  784. items, err := getter(models.ListOptions{Page: pager.Paginater.Current()})
  785. if err != nil {
  786. ctx.ServerError("getter", err)
  787. return
  788. }
  789. ctx.Data["Cards"] = items
  790. ctx.HTML(200, tpl)
  791. }
  792. // Watchers render repository's watch users
  793. func Watchers(ctx *context.Context) {
  794. ctx.Data["Title"] = ctx.Tr("repo.watchers")
  795. ctx.Data["CardsTitle"] = ctx.Tr("repo.watchers")
  796. ctx.Data["PageIsWatchers"] = true
  797. RenderUserCards(ctx, ctx.Repo.Repository.NumWatches, ctx.Repo.Repository.GetWatchers, tplWatchers)
  798. }
  799. // Stars render repository's starred users
  800. func Stars(ctx *context.Context) {
  801. ctx.Data["Title"] = ctx.Tr("repo.stargazers")
  802. ctx.Data["CardsTitle"] = ctx.Tr("repo.stargazers")
  803. ctx.Data["PageIsStargazers"] = true
  804. RenderUserCards(ctx, ctx.Repo.Repository.NumStars, ctx.Repo.Repository.GetStargazers, tplWatchers)
  805. }
  806. // Forks render repository's forked users
  807. func Forks(ctx *context.Context) {
  808. ctx.Data["Title"] = ctx.Tr("repos.forks")
  809. forks, err := ctx.Repo.Repository.GetForks(models.ListOptions{})
  810. if err != nil {
  811. ctx.ServerError("GetForks", err)
  812. return
  813. }
  814. for _, fork := range forks {
  815. if err = fork.GetOwner(); err != nil {
  816. ctx.ServerError("GetOwner", err)
  817. return
  818. }
  819. }
  820. ctx.Data["Forks"] = forks
  821. ctx.HTML(200, tplForks)
  822. }
  823. func Contributors(ctx *context.Context) {
  824. ctx.HTML(http.StatusOK, tplContributors)
  825. }
  826. func ContributorsAPI(ctx *context.Context) {
  827. count := 0
  828. errorCode := 0
  829. errorMsg := ""
  830. contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath())
  831. var contributorInfos []*ContributorInfo
  832. if err == nil && contributors != nil {
  833. contributorInfoHash := make(map[string]*ContributorInfo)
  834. for _, c := range contributors {
  835. if strings.Compare(c.Email, "") == 0 {
  836. continue
  837. }
  838. // get user info from committer email
  839. user, err := models.GetUserByActivateEmail(c.Email)
  840. if err == nil {
  841. // committer is system user, get info through user's primary email
  842. if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
  843. // existed: same primary email, different committer name
  844. existedContributorInfo.CommitCnt += c.CommitCnt
  845. } else {
  846. // new committer info
  847. var newContributor = &ContributorInfo{
  848. user, user.RelAvatarLink(),user.Name, user.Email,c.CommitCnt,
  849. }
  850. count++
  851. contributorInfos = append(contributorInfos, newContributor)
  852. contributorInfoHash[user.Email] = newContributor
  853. }
  854. } else {
  855. // committer is not system user
  856. if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
  857. // existed: same primary email, different committer name
  858. existedContributorInfo.CommitCnt += c.CommitCnt
  859. } else {
  860. var newContributor = &ContributorInfo{
  861. user, "", "",c.Email,c.CommitCnt,
  862. }
  863. count++
  864. contributorInfos = append(contributorInfos, newContributor)
  865. contributorInfoHash[c.Email] = newContributor
  866. }
  867. }
  868. }
  869. } else {
  870. log.Error("GetContributors failed: %v", err)
  871. errorCode = -1
  872. errorMsg = err.Error()
  873. }
  874. ctx.JSON(http.StatusOK, GetContributorsInfo{
  875. ErrorCode: errorCode,
  876. ErrorMsg: errorMsg,
  877. Count: count,
  878. ContributorInfo: contributorInfos,
  879. })
  880. }