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.

repo.go 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package context
  5. import (
  6. "context"
  7. "fmt"
  8. "html"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/models/db"
  15. git_model "code.gitea.io/gitea/models/git"
  16. issues_model "code.gitea.io/gitea/models/issues"
  17. access_model "code.gitea.io/gitea/models/perm/access"
  18. repo_model "code.gitea.io/gitea/models/repo"
  19. unit_model "code.gitea.io/gitea/models/unit"
  20. user_model "code.gitea.io/gitea/models/user"
  21. "code.gitea.io/gitea/modules/cache"
  22. "code.gitea.io/gitea/modules/git"
  23. code_indexer "code.gitea.io/gitea/modules/indexer/code"
  24. "code.gitea.io/gitea/modules/issue/template"
  25. "code.gitea.io/gitea/modules/log"
  26. repo_module "code.gitea.io/gitea/modules/repository"
  27. "code.gitea.io/gitea/modules/setting"
  28. api "code.gitea.io/gitea/modules/structs"
  29. "code.gitea.io/gitea/modules/util"
  30. asymkey_service "code.gitea.io/gitea/services/asymkey"
  31. "github.com/editorconfig/editorconfig-core-go/v2"
  32. )
  33. // IssueTemplateDirCandidates issue templates directory
  34. var IssueTemplateDirCandidates = []string{
  35. "ISSUE_TEMPLATE",
  36. "issue_template",
  37. ".gitea/ISSUE_TEMPLATE",
  38. ".gitea/issue_template",
  39. ".github/ISSUE_TEMPLATE",
  40. ".github/issue_template",
  41. ".gitlab/ISSUE_TEMPLATE",
  42. ".gitlab/issue_template",
  43. }
  44. // PullRequest contains information to make a pull request
  45. type PullRequest struct {
  46. BaseRepo *repo_model.Repository
  47. Allowed bool
  48. SameRepo bool
  49. HeadInfoSubURL string // [<user>:]<branch> url segment
  50. }
  51. // Repository contains information to operate a repository
  52. type Repository struct {
  53. access_model.Permission
  54. IsWatching bool
  55. IsViewBranch bool
  56. IsViewTag bool
  57. IsViewCommit bool
  58. Repository *repo_model.Repository
  59. Owner *user_model.User
  60. Commit *git.Commit
  61. Tag *git.Tag
  62. GitRepo *git.Repository
  63. RefName string
  64. BranchName string
  65. TagName string
  66. TreePath string
  67. CommitID string
  68. RepoLink string
  69. CloneLink repo_model.CloneLink
  70. CommitsCount int64
  71. Mirror *repo_model.Mirror
  72. PullRequest *PullRequest
  73. }
  74. // CanWriteToBranch checks if the branch is writable by the user
  75. func (r *Repository) CanWriteToBranch(user *user_model.User, branch string) bool {
  76. return issues_model.CanMaintainerWriteToBranch(r.Permission, branch, user)
  77. }
  78. // CanEnableEditor returns true if repository is editable and user has proper access level.
  79. func (r *Repository) CanEnableEditor(user *user_model.User) bool {
  80. return r.IsViewBranch && r.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
  81. }
  82. // CanCreateBranch returns true if repository is editable and user has proper access level.
  83. func (r *Repository) CanCreateBranch() bool {
  84. return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
  85. }
  86. // RepoMustNotBeArchived checks if a repo is archived
  87. func RepoMustNotBeArchived() func(ctx *Context) {
  88. return func(ctx *Context) {
  89. if ctx.Repo.Repository.IsArchived {
  90. ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
  91. }
  92. }
  93. }
  94. // CanCommitToBranchResults represents the results of CanCommitToBranch
  95. type CanCommitToBranchResults struct {
  96. CanCommitToBranch bool
  97. EditorEnabled bool
  98. UserCanPush bool
  99. RequireSigned bool
  100. WillSign bool
  101. SigningKey string
  102. WontSignReason string
  103. }
  104. // CanCommitToBranch returns true if repository is editable and user has proper access level
  105. //
  106. // and branch is not protected for push
  107. func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
  108. protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, r.Repository.ID, r.BranchName)
  109. if err != nil {
  110. return CanCommitToBranchResults{}, err
  111. }
  112. userCanPush := true
  113. requireSigned := false
  114. if protectedBranch != nil {
  115. protectedBranch.Repo = r.Repository
  116. userCanPush = protectedBranch.CanUserPush(ctx, doer)
  117. requireSigned = protectedBranch.RequireSignedCommits
  118. }
  119. sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
  120. canCommit := r.CanEnableEditor(doer) && userCanPush
  121. if requireSigned {
  122. canCommit = canCommit && sign
  123. }
  124. wontSignReason := ""
  125. if err != nil {
  126. if asymkey_service.IsErrWontSign(err) {
  127. wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
  128. err = nil
  129. } else {
  130. wontSignReason = "error"
  131. }
  132. }
  133. return CanCommitToBranchResults{
  134. CanCommitToBranch: canCommit,
  135. EditorEnabled: r.CanEnableEditor(doer),
  136. UserCanPush: userCanPush,
  137. RequireSigned: requireSigned,
  138. WillSign: sign,
  139. SigningKey: keyID,
  140. WontSignReason: wontSignReason,
  141. }, err
  142. }
  143. // CanUseTimetracker returns whether or not a user can use the timetracker.
  144. func (r *Repository) CanUseTimetracker(issue *issues_model.Issue, user *user_model.User) bool {
  145. // Checking for following:
  146. // 1. Is timetracker enabled
  147. // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
  148. isAssigned, _ := issues_model.IsUserAssignedToIssue(db.DefaultContext, issue, user)
  149. return r.Repository.IsTimetrackerEnabled(db.DefaultContext) && (!r.Repository.AllowOnlyContributorsToTrackTime(db.DefaultContext) ||
  150. r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
  151. }
  152. // CanCreateIssueDependencies returns whether or not a user can create dependencies.
  153. func (r *Repository) CanCreateIssueDependencies(user *user_model.User, isPull bool) bool {
  154. return r.Repository.IsDependenciesEnabled(db.DefaultContext) && r.Permission.CanWriteIssuesOrPulls(isPull)
  155. }
  156. // GetCommitsCount returns cached commit count for current view
  157. func (r *Repository) GetCommitsCount() (int64, error) {
  158. var contextName string
  159. if r.IsViewBranch {
  160. contextName = r.BranchName
  161. } else if r.IsViewTag {
  162. contextName = r.TagName
  163. } else {
  164. contextName = r.CommitID
  165. }
  166. return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, r.IsViewBranch || r.IsViewTag), func() (int64, error) {
  167. return r.Commit.CommitsCount()
  168. })
  169. }
  170. // GetCommitGraphsCount returns cached commit count for current view
  171. func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) {
  172. cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
  173. return cache.GetInt64(cacheKey, func() (int64, error) {
  174. if len(branches) == 0 {
  175. return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...)
  176. }
  177. return git.CommitsCountFiles(ctx, r.Repository.RepoPath(), branches, files)
  178. })
  179. }
  180. // BranchNameSubURL sub-URL for the BranchName field
  181. func (r *Repository) BranchNameSubURL() string {
  182. switch {
  183. case r.IsViewBranch:
  184. return "branch/" + util.PathEscapeSegments(r.BranchName)
  185. case r.IsViewTag:
  186. return "tag/" + util.PathEscapeSegments(r.TagName)
  187. case r.IsViewCommit:
  188. return "commit/" + util.PathEscapeSegments(r.CommitID)
  189. }
  190. log.Error("Unknown view type for repo: %v", r)
  191. return ""
  192. }
  193. // FileExists returns true if a file exists in the given repo branch
  194. func (r *Repository) FileExists(path, branch string) (bool, error) {
  195. if branch == "" {
  196. branch = r.Repository.DefaultBranch
  197. }
  198. commit, err := r.GitRepo.GetBranchCommit(branch)
  199. if err != nil {
  200. return false, err
  201. }
  202. if _, err := commit.GetTreeEntryByPath(path); err != nil {
  203. return false, err
  204. }
  205. return true, nil
  206. }
  207. // GetEditorconfig returns the .editorconfig definition if found in the
  208. // HEAD of the default repo branch.
  209. func (r *Repository) GetEditorconfig(optCommit ...*git.Commit) (*editorconfig.Editorconfig, error) {
  210. if r.GitRepo == nil {
  211. return nil, nil
  212. }
  213. var (
  214. err error
  215. commit *git.Commit
  216. )
  217. if len(optCommit) != 0 {
  218. commit = optCommit[0]
  219. } else {
  220. commit, err = r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
  221. if err != nil {
  222. return nil, err
  223. }
  224. }
  225. treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
  226. if err != nil {
  227. return nil, err
  228. }
  229. if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
  230. return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
  231. }
  232. reader, err := treeEntry.Blob().DataAsync()
  233. if err != nil {
  234. return nil, err
  235. }
  236. defer reader.Close()
  237. return editorconfig.Parse(reader)
  238. }
  239. // RetrieveBaseRepo retrieves base repository
  240. func RetrieveBaseRepo(ctx *Context, repo *repo_model.Repository) {
  241. // Non-fork repository will not return error in this method.
  242. if err := repo.GetBaseRepo(ctx); err != nil {
  243. if repo_model.IsErrRepoNotExist(err) {
  244. repo.IsFork = false
  245. repo.ForkID = 0
  246. return
  247. }
  248. ctx.ServerError("GetBaseRepo", err)
  249. return
  250. } else if err = repo.BaseRepo.GetOwner(ctx); err != nil {
  251. ctx.ServerError("BaseRepo.GetOwner", err)
  252. return
  253. }
  254. }
  255. // RetrieveTemplateRepo retrieves template repository used to generate this repository
  256. func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) {
  257. // Non-generated repository will not return error in this method.
  258. templateRepo, err := repo_model.GetTemplateRepo(ctx, repo)
  259. if err != nil {
  260. if repo_model.IsErrRepoNotExist(err) {
  261. repo.TemplateID = 0
  262. return
  263. }
  264. ctx.ServerError("GetTemplateRepo", err)
  265. return
  266. } else if err = templateRepo.GetOwner(ctx); err != nil {
  267. ctx.ServerError("TemplateRepo.GetOwner", err)
  268. return
  269. }
  270. perm, err := access_model.GetUserRepoPermission(ctx, templateRepo, ctx.Doer)
  271. if err != nil {
  272. ctx.ServerError("GetUserRepoPermission", err)
  273. return
  274. }
  275. if !perm.CanRead(unit_model.TypeCode) {
  276. repo.TemplateID = 0
  277. }
  278. }
  279. // ComposeGoGetImport returns go-get-import meta content.
  280. func ComposeGoGetImport(owner, repo string) string {
  281. /// setting.AppUrl is guaranteed to be parse as url
  282. appURL, _ := url.Parse(setting.AppURL)
  283. return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
  284. }
  285. // EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
  286. // if user does not have actual access to the requested repository,
  287. // or the owner or repository does not exist at all.
  288. // This is particular a workaround for "go get" command which does not respect
  289. // .netrc file.
  290. func EarlyResponseForGoGetMeta(ctx *Context) {
  291. username := ctx.Params(":username")
  292. reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
  293. if username == "" || reponame == "" {
  294. ctx.PlainText(http.StatusBadRequest, "invalid repository path")
  295. return
  296. }
  297. goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), repo_model.ComposeHTTPSCloneURL(username, reponame))
  298. htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
  299. ctx.PlainText(http.StatusOK, htmlMeta)
  300. }
  301. // RedirectToRepo redirect to a differently-named repository
  302. func RedirectToRepo(ctx *Context, redirectRepoID int64) {
  303. ownerName := ctx.Params(":username")
  304. previousRepoName := ctx.Params(":reponame")
  305. repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID)
  306. if err != nil {
  307. ctx.ServerError("GetRepositoryByID", err)
  308. return
  309. }
  310. redirectPath := strings.Replace(
  311. ctx.Req.URL.EscapedPath(),
  312. url.PathEscape(ownerName)+"/"+url.PathEscape(previousRepoName),
  313. url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name),
  314. 1,
  315. )
  316. if ctx.Req.URL.RawQuery != "" {
  317. redirectPath += "?" + ctx.Req.URL.RawQuery
  318. }
  319. ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
  320. }
  321. func repoAssignment(ctx *Context, repo *repo_model.Repository) {
  322. var err error
  323. if err = repo.GetOwner(ctx); err != nil {
  324. ctx.ServerError("GetOwner", err)
  325. return
  326. }
  327. ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  328. if err != nil {
  329. ctx.ServerError("GetUserRepoPermission", err)
  330. return
  331. }
  332. // Check access.
  333. if !ctx.Repo.Permission.HasAccess() {
  334. if ctx.FormString("go-get") == "1" {
  335. EarlyResponseForGoGetMeta(ctx)
  336. return
  337. }
  338. ctx.NotFound("no access right", nil)
  339. return
  340. }
  341. ctx.Data["HasAccess"] = true
  342. ctx.Data["Permission"] = &ctx.Repo.Permission
  343. if repo.IsMirror {
  344. ctx.Repo.Mirror, err = repo_model.GetMirrorByRepoID(ctx, repo.ID)
  345. if err == nil {
  346. ctx.Repo.Mirror.Repo = repo
  347. ctx.Data["IsPullMirror"] = true
  348. ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
  349. ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
  350. ctx.Data["Mirror"] = ctx.Repo.Mirror
  351. } else if err != repo_model.ErrMirrorNotExist {
  352. ctx.ServerError("GetMirrorByRepoID", err)
  353. return
  354. }
  355. }
  356. pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
  357. if err != nil {
  358. ctx.ServerError("GetPushMirrorsByRepoID", err)
  359. return
  360. }
  361. ctx.Repo.Repository = repo
  362. ctx.Data["PushMirrors"] = pushMirrors
  363. ctx.Data["RepoName"] = ctx.Repo.Repository.Name
  364. ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
  365. }
  366. // RepoIDAssignment returns a handler which assigns the repo to the context.
  367. func RepoIDAssignment() func(ctx *Context) {
  368. return func(ctx *Context) {
  369. repoID := ctx.ParamsInt64(":repoid")
  370. // Get repository.
  371. repo, err := repo_model.GetRepositoryByID(ctx, repoID)
  372. if err != nil {
  373. if repo_model.IsErrRepoNotExist(err) {
  374. ctx.NotFound("GetRepositoryByID", nil)
  375. } else {
  376. ctx.ServerError("GetRepositoryByID", err)
  377. }
  378. return
  379. }
  380. repoAssignment(ctx, repo)
  381. }
  382. }
  383. // RepoAssignment returns a middleware to handle repository assignment
  384. func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
  385. if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
  386. log.Trace("RepoAssignment was exec already, skipping second call ...")
  387. return
  388. }
  389. ctx.Data["repoAssignmentExecuted"] = true
  390. var (
  391. owner *user_model.User
  392. err error
  393. )
  394. userName := ctx.Params(":username")
  395. repoName := ctx.Params(":reponame")
  396. repoName = strings.TrimSuffix(repoName, ".git")
  397. if setting.EnableFeed {
  398. repoName = strings.TrimSuffix(repoName, ".rss")
  399. repoName = strings.TrimSuffix(repoName, ".atom")
  400. }
  401. // Check if the user is the same as the repository owner
  402. if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
  403. owner = ctx.Doer
  404. } else {
  405. owner, err = user_model.GetUserByName(ctx, userName)
  406. if err != nil {
  407. if user_model.IsErrUserNotExist(err) {
  408. // go-get does not support redirects
  409. // https://github.com/golang/go/issues/19760
  410. if ctx.FormString("go-get") == "1" {
  411. EarlyResponseForGoGetMeta(ctx)
  412. return
  413. }
  414. if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil {
  415. RedirectToUser(ctx, userName, redirectUserID)
  416. } else if user_model.IsErrUserRedirectNotExist(err) {
  417. ctx.NotFound("GetUserByName", nil)
  418. } else {
  419. ctx.ServerError("LookupUserRedirect", err)
  420. }
  421. } else {
  422. ctx.ServerError("GetUserByName", err)
  423. }
  424. return
  425. }
  426. }
  427. ctx.Repo.Owner = owner
  428. ctx.ContextUser = owner
  429. ctx.Data["Username"] = ctx.Repo.Owner.Name
  430. // redirect link to wiki
  431. if strings.HasSuffix(repoName, ".wiki") {
  432. // ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
  433. // Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
  434. originalRepoName := ctx.Params(":reponame")
  435. redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
  436. redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
  437. redirectPath := strings.Replace(
  438. ctx.Req.URL.EscapedPath(),
  439. url.PathEscape(userName)+"/"+url.PathEscape(originalRepoName),
  440. url.PathEscape(userName)+"/"+url.PathEscape(redirectRepoName)+"/wiki",
  441. 1,
  442. )
  443. if ctx.Req.URL.RawQuery != "" {
  444. redirectPath += "?" + ctx.Req.URL.RawQuery
  445. }
  446. ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
  447. return
  448. }
  449. // Get repository.
  450. repo, err := repo_model.GetRepositoryByName(owner.ID, repoName)
  451. if err != nil {
  452. if repo_model.IsErrRepoNotExist(err) {
  453. redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName)
  454. if err == nil {
  455. RedirectToRepo(ctx, redirectRepoID)
  456. } else if repo_model.IsErrRedirectNotExist(err) {
  457. if ctx.FormString("go-get") == "1" {
  458. EarlyResponseForGoGetMeta(ctx)
  459. return
  460. }
  461. ctx.NotFound("GetRepositoryByName", nil)
  462. } else {
  463. ctx.ServerError("LookupRepoRedirect", err)
  464. }
  465. } else {
  466. ctx.ServerError("GetRepositoryByName", err)
  467. }
  468. return
  469. }
  470. repo.Owner = owner
  471. repoAssignment(ctx, repo)
  472. if ctx.Written() {
  473. return
  474. }
  475. ctx.Repo.RepoLink = repo.Link()
  476. ctx.Data["RepoLink"] = ctx.Repo.RepoLink
  477. ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  478. unit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeExternalTracker)
  479. if err == nil {
  480. ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
  481. }
  482. ctx.Data["NumTags"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
  483. IncludeDrafts: true,
  484. IncludeTags: true,
  485. HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags
  486. })
  487. if err != nil {
  488. ctx.ServerError("GetReleaseCountByRepoID", err)
  489. return
  490. }
  491. ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{})
  492. if err != nil {
  493. ctx.ServerError("GetReleaseCountByRepoID", err)
  494. return
  495. }
  496. ctx.Data["Title"] = owner.Name + "/" + repo.Name
  497. ctx.Data["Repository"] = repo
  498. ctx.Data["Owner"] = ctx.Repo.Repository.Owner
  499. ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
  500. ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
  501. ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
  502. ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode)
  503. ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
  504. ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
  505. canSignedUserFork, err := repo_module.CanUserForkRepo(ctx.Doer, ctx.Repo.Repository)
  506. if err != nil {
  507. ctx.ServerError("CanUserForkRepo", err)
  508. return
  509. }
  510. ctx.Data["CanSignedUserFork"] = canSignedUserFork
  511. userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
  512. if err != nil {
  513. ctx.ServerError("GetForksByUserAndOrgs", err)
  514. return
  515. }
  516. ctx.Data["UserAndOrgForks"] = userAndOrgForks
  517. // canSignedUserFork is true if the current user doesn't have a fork of this repo yet or
  518. // if he owns an org that doesn't have a fork of this repo yet
  519. // If multiple forks are available or if the user can fork to another account, but there is already a fork: open selection dialog
  520. ctx.Data["ShowForkModal"] = len(userAndOrgForks) > 1 || (canSignedUserFork && len(userAndOrgForks) > 0)
  521. ctx.Data["RepoCloneLink"] = repo.CloneLink()
  522. cloneButtonShowHTTPS := !setting.Repository.DisableHTTPGit
  523. cloneButtonShowSSH := !setting.SSH.Disabled && (ctx.IsSigned || setting.SSH.ExposeAnonymous)
  524. if !cloneButtonShowHTTPS && !cloneButtonShowSSH {
  525. // We have to show at least one link, so we just show the HTTPS
  526. cloneButtonShowHTTPS = true
  527. }
  528. ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
  529. ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
  530. ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware
  531. ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
  532. if setting.Indexer.RepoIndexerEnabled {
  533. ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable()
  534. }
  535. if ctx.IsSigned {
  536. ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.Doer.ID, repo.ID)
  537. ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID)
  538. }
  539. if repo.IsFork {
  540. RetrieveBaseRepo(ctx, repo)
  541. if ctx.Written() {
  542. return
  543. }
  544. }
  545. if repo.IsGenerated() {
  546. RetrieveTemplateRepo(ctx, repo)
  547. if ctx.Written() {
  548. return
  549. }
  550. }
  551. isHomeOrSettings := ctx.Link == ctx.Repo.RepoLink || ctx.Link == ctx.Repo.RepoLink+"/settings" || strings.HasPrefix(ctx.Link, ctx.Repo.RepoLink+"/settings/")
  552. // Disable everything when the repo is being created
  553. if ctx.Repo.Repository.IsBeingCreated() || ctx.Repo.Repository.IsBroken() {
  554. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  555. if !isHomeOrSettings {
  556. ctx.Redirect(ctx.Repo.RepoLink)
  557. }
  558. return
  559. }
  560. gitRepo, err := git.OpenRepository(ctx, repo_model.RepoPath(userName, repoName))
  561. if err != nil {
  562. if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
  563. log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
  564. ctx.Repo.Repository.Status = repo_model.RepositoryBroken
  565. ctx.Repo.Repository.IsEmpty = true
  566. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  567. // Only allow access to base of repo or settings
  568. if !isHomeOrSettings {
  569. ctx.Redirect(ctx.Repo.RepoLink)
  570. }
  571. return
  572. }
  573. ctx.ServerError("RepoAssignment Invalid repo "+repo_model.RepoPath(userName, repoName), err)
  574. return
  575. }
  576. if ctx.Repo.GitRepo != nil {
  577. ctx.Repo.GitRepo.Close()
  578. }
  579. ctx.Repo.GitRepo = gitRepo
  580. // We opened it, we should close it
  581. cancel = func() {
  582. // If it's been set to nil then assume someone else has closed it.
  583. if ctx.Repo.GitRepo != nil {
  584. ctx.Repo.GitRepo.Close()
  585. }
  586. }
  587. // Stop at this point when the repo is empty.
  588. if ctx.Repo.Repository.IsEmpty {
  589. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  590. return
  591. }
  592. tags, err := ctx.Repo.GitRepo.GetTags(0, 0)
  593. if err != nil {
  594. if strings.Contains(err.Error(), "fatal: not a git repository ") {
  595. log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
  596. ctx.Repo.Repository.Status = repo_model.RepositoryBroken
  597. ctx.Repo.Repository.IsEmpty = true
  598. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  599. // Only allow access to base of repo or settings
  600. if !isHomeOrSettings {
  601. ctx.Redirect(ctx.Repo.RepoLink)
  602. }
  603. return
  604. }
  605. ctx.ServerError("GetTags", err)
  606. return
  607. }
  608. ctx.Data["Tags"] = tags
  609. brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
  610. if err != nil {
  611. ctx.ServerError("GetBranches", err)
  612. return
  613. }
  614. ctx.Data["Branches"] = brs
  615. ctx.Data["BranchesCount"] = len(brs)
  616. // If not branch selected, try default one.
  617. // If default branch doesn't exists, fall back to some other branch.
  618. if len(ctx.Repo.BranchName) == 0 {
  619. if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
  620. ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
  621. } else if len(brs) > 0 {
  622. ctx.Repo.BranchName = brs[0]
  623. }
  624. ctx.Repo.RefName = ctx.Repo.BranchName
  625. }
  626. ctx.Data["BranchName"] = ctx.Repo.BranchName
  627. // People who have push access or have forked repository can propose a new pull request.
  628. canPush := ctx.Repo.CanWrite(unit_model.TypeCode) ||
  629. (ctx.IsSigned && repo_model.HasForkedRepo(ctx.Doer.ID, ctx.Repo.Repository.ID))
  630. canCompare := false
  631. // Pull request is allowed if this is a fork repository
  632. // and base repository accepts pull requests.
  633. if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
  634. canCompare = true
  635. ctx.Data["BaseRepo"] = repo.BaseRepo
  636. ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
  637. ctx.Repo.PullRequest.Allowed = canPush
  638. ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
  639. } else if repo.AllowsPulls() {
  640. // Or, this is repository accepts pull requests between branches.
  641. canCompare = true
  642. ctx.Data["BaseRepo"] = repo
  643. ctx.Repo.PullRequest.BaseRepo = repo
  644. ctx.Repo.PullRequest.Allowed = canPush
  645. ctx.Repo.PullRequest.SameRepo = true
  646. ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName)
  647. }
  648. ctx.Data["CanCompareOrPull"] = canCompare
  649. ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
  650. if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
  651. repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
  652. if err != nil {
  653. ctx.ServerError("GetPendingRepositoryTransfer", err)
  654. return
  655. }
  656. if err := repoTransfer.LoadAttributes(ctx); err != nil {
  657. ctx.ServerError("LoadRecipient", err)
  658. return
  659. }
  660. ctx.Data["RepoTransfer"] = repoTransfer
  661. if ctx.Doer != nil {
  662. ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx.Doer)
  663. }
  664. }
  665. if ctx.FormString("go-get") == "1" {
  666. ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
  667. prefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
  668. ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
  669. ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
  670. }
  671. return cancel
  672. }
  673. // RepoRefType type of repo reference
  674. type RepoRefType int
  675. const (
  676. // RepoRefLegacy unknown type, make educated guess and redirect.
  677. // for backward compatibility with previous URL scheme
  678. RepoRefLegacy RepoRefType = iota
  679. // RepoRefAny is for usage where educated guess is needed
  680. // but redirect can not be made
  681. RepoRefAny
  682. // RepoRefBranch branch
  683. RepoRefBranch
  684. // RepoRefTag tag
  685. RepoRefTag
  686. // RepoRefCommit commit
  687. RepoRefCommit
  688. // RepoRefBlob blob
  689. RepoRefBlob
  690. )
  691. // RepoRef handles repository reference names when the ref name is not
  692. // explicitly given
  693. func RepoRef() func(*Context) context.CancelFunc {
  694. // since no ref name is explicitly specified, ok to just use branch
  695. return RepoRefByType(RepoRefBranch)
  696. }
  697. // RefTypeIncludesBranches returns true if ref type can be a branch
  698. func (rt RepoRefType) RefTypeIncludesBranches() bool {
  699. if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch {
  700. return true
  701. }
  702. return false
  703. }
  704. // RefTypeIncludesTags returns true if ref type can be a tag
  705. func (rt RepoRefType) RefTypeIncludesTags() bool {
  706. if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag {
  707. return true
  708. }
  709. return false
  710. }
  711. func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string {
  712. refName := ""
  713. parts := strings.Split(path, "/")
  714. for i, part := range parts {
  715. refName = strings.TrimPrefix(refName+"/"+part, "/")
  716. if isExist(refName) {
  717. ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
  718. return refName
  719. }
  720. }
  721. return ""
  722. }
  723. func getRefName(ctx *Context, pathType RepoRefType) string {
  724. path := ctx.Params("*")
  725. switch pathType {
  726. case RepoRefLegacy, RepoRefAny:
  727. if refName := getRefName(ctx, RepoRefBranch); len(refName) > 0 {
  728. return refName
  729. }
  730. if refName := getRefName(ctx, RepoRefTag); len(refName) > 0 {
  731. return refName
  732. }
  733. // For legacy and API support only full commit sha
  734. parts := strings.Split(path, "/")
  735. if len(parts) > 0 && len(parts[0]) == git.SHAFullLength {
  736. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  737. return parts[0]
  738. }
  739. if refName := getRefName(ctx, RepoRefBlob); len(refName) > 0 {
  740. return refName
  741. }
  742. ctx.Repo.TreePath = path
  743. return ctx.Repo.Repository.DefaultBranch
  744. case RepoRefBranch:
  745. ref := getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist)
  746. if len(ref) == 0 {
  747. // maybe it's a renamed branch
  748. return getRefNameFromPath(ctx, path, func(s string) bool {
  749. b, exist, err := git_model.FindRenamedBranch(ctx, ctx.Repo.Repository.ID, s)
  750. if err != nil {
  751. log.Error("FindRenamedBranch", err)
  752. return false
  753. }
  754. if !exist {
  755. return false
  756. }
  757. ctx.Data["IsRenamedBranch"] = true
  758. ctx.Data["RenamedBranchName"] = b.To
  759. return true
  760. })
  761. }
  762. return ref
  763. case RepoRefTag:
  764. return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
  765. case RepoRefCommit:
  766. parts := strings.Split(path, "/")
  767. if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength {
  768. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  769. return parts[0]
  770. }
  771. case RepoRefBlob:
  772. _, err := ctx.Repo.GitRepo.GetBlob(path)
  773. if err != nil {
  774. return ""
  775. }
  776. return path
  777. default:
  778. log.Error("Unrecognized path type: %v", path)
  779. }
  780. return ""
  781. }
  782. // RepoRefByType handles repository reference name for a specific type
  783. // of repository reference
  784. func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc {
  785. return func(ctx *Context) (cancel context.CancelFunc) {
  786. // Empty repository does not have reference information.
  787. if ctx.Repo.Repository.IsEmpty {
  788. return
  789. }
  790. var (
  791. refName string
  792. err error
  793. )
  794. if ctx.Repo.GitRepo == nil {
  795. repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  796. ctx.Repo.GitRepo, err = git.OpenRepository(ctx, repoPath)
  797. if err != nil {
  798. ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
  799. return
  800. }
  801. // We opened it, we should close it
  802. cancel = func() {
  803. // If it's been set to nil then assume someone else has closed it.
  804. if ctx.Repo.GitRepo != nil {
  805. ctx.Repo.GitRepo.Close()
  806. }
  807. }
  808. }
  809. // Get default branch.
  810. if len(ctx.Params("*")) == 0 {
  811. refName = ctx.Repo.Repository.DefaultBranch
  812. if !ctx.Repo.GitRepo.IsBranchExist(refName) {
  813. brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
  814. if err != nil {
  815. ctx.ServerError("GetBranches", err)
  816. return
  817. } else if len(brs) == 0 {
  818. err = fmt.Errorf("No branches in non-empty repository %s",
  819. ctx.Repo.GitRepo.Path)
  820. ctx.ServerError("GetBranches", err)
  821. return
  822. }
  823. refName = brs[0]
  824. }
  825. ctx.Repo.RefName = refName
  826. ctx.Repo.BranchName = refName
  827. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  828. if err != nil {
  829. ctx.ServerError("GetBranchCommit", err)
  830. return
  831. }
  832. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  833. ctx.Repo.IsViewBranch = true
  834. } else {
  835. refName = getRefName(ctx, refType)
  836. ctx.Repo.RefName = refName
  837. isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
  838. if isRenamedBranch && has {
  839. renamedBranchName := ctx.Data["RenamedBranchName"].(string)
  840. ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName))
  841. link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1)
  842. ctx.Redirect(link)
  843. return
  844. }
  845. if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
  846. ctx.Repo.IsViewBranch = true
  847. ctx.Repo.BranchName = refName
  848. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  849. if err != nil {
  850. ctx.ServerError("GetBranchCommit", err)
  851. return
  852. }
  853. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  854. } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
  855. ctx.Repo.IsViewTag = true
  856. ctx.Repo.TagName = refName
  857. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
  858. if err != nil {
  859. if git.IsErrNotExist(err) {
  860. ctx.NotFound("GetTagCommit", err)
  861. return
  862. }
  863. ctx.ServerError("GetTagCommit", err)
  864. return
  865. }
  866. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  867. } else if len(refName) >= 7 && len(refName) <= git.SHAFullLength {
  868. ctx.Repo.IsViewCommit = true
  869. ctx.Repo.CommitID = refName
  870. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
  871. if err != nil {
  872. ctx.NotFound("GetCommit", err)
  873. return
  874. }
  875. // If short commit ID add canonical link header
  876. if len(refName) < git.SHAFullLength {
  877. ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
  878. util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
  879. }
  880. } else {
  881. if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
  882. return
  883. }
  884. ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
  885. return
  886. }
  887. if refType == RepoRefLegacy {
  888. // redirect from old URL scheme to new URL scheme
  889. prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
  890. ctx.Redirect(path.Join(
  891. ctx.Repo.RepoLink,
  892. util.PathEscapeSegments(prefix),
  893. ctx.Repo.BranchNameSubURL(),
  894. util.PathEscapeSegments(ctx.Repo.TreePath)))
  895. return
  896. }
  897. }
  898. ctx.Data["BranchName"] = ctx.Repo.BranchName
  899. ctx.Data["RefName"] = ctx.Repo.RefName
  900. ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
  901. ctx.Data["TagName"] = ctx.Repo.TagName
  902. ctx.Data["CommitID"] = ctx.Repo.CommitID
  903. ctx.Data["TreePath"] = ctx.Repo.TreePath
  904. ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
  905. ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
  906. ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
  907. ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
  908. ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
  909. if err != nil {
  910. ctx.ServerError("GetCommitsCount", err)
  911. return
  912. }
  913. ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
  914. ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
  915. return cancel
  916. }
  917. }
  918. // GitHookService checks if repository Git hooks service has been enabled.
  919. func GitHookService() func(ctx *Context) {
  920. return func(ctx *Context) {
  921. if !ctx.Doer.CanEditGitHook() {
  922. ctx.NotFound("GitHookService", nil)
  923. return
  924. }
  925. }
  926. }
  927. // UnitTypes returns a middleware to set unit types to context variables.
  928. func UnitTypes() func(ctx *Context) {
  929. return func(ctx *Context) {
  930. ctx.Data["UnitTypeCode"] = unit_model.TypeCode
  931. ctx.Data["UnitTypeIssues"] = unit_model.TypeIssues
  932. ctx.Data["UnitTypePullRequests"] = unit_model.TypePullRequests
  933. ctx.Data["UnitTypeReleases"] = unit_model.TypeReleases
  934. ctx.Data["UnitTypeWiki"] = unit_model.TypeWiki
  935. ctx.Data["UnitTypeExternalWiki"] = unit_model.TypeExternalWiki
  936. ctx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker
  937. ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects
  938. ctx.Data["UnitTypePackages"] = unit_model.TypePackages
  939. ctx.Data["UnitTypeActions"] = unit_model.TypeActions
  940. }
  941. }
  942. // IssueTemplatesFromDefaultBranch checks for valid issue templates in the repo's default branch,
  943. func (ctx *Context) IssueTemplatesFromDefaultBranch() []*api.IssueTemplate {
  944. ret, _ := ctx.IssueTemplatesErrorsFromDefaultBranch()
  945. return ret
  946. }
  947. // IssueTemplatesErrorsFromDefaultBranch checks for issue templates in the repo's default branch,
  948. // returns valid templates and the errors of invalid template files.
  949. func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplate, map[string]error) {
  950. var issueTemplates []*api.IssueTemplate
  951. if ctx.Repo.Repository.IsEmpty {
  952. return issueTemplates, nil
  953. }
  954. if ctx.Repo.Commit == nil {
  955. var err error
  956. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  957. if err != nil {
  958. return issueTemplates, nil
  959. }
  960. }
  961. invalidFiles := map[string]error{}
  962. for _, dirName := range IssueTemplateDirCandidates {
  963. tree, err := ctx.Repo.Commit.SubTree(dirName)
  964. if err != nil {
  965. log.Debug("get sub tree of %s: %v", dirName, err)
  966. continue
  967. }
  968. entries, err := tree.ListEntries()
  969. if err != nil {
  970. log.Debug("list entries in %s: %v", dirName, err)
  971. return issueTemplates, nil
  972. }
  973. for _, entry := range entries {
  974. if !template.CouldBe(entry.Name()) {
  975. continue
  976. }
  977. fullName := path.Join(dirName, entry.Name())
  978. if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
  979. invalidFiles[fullName] = err
  980. } else {
  981. if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
  982. it.Ref = git.BranchPrefix + it.Ref
  983. }
  984. issueTemplates = append(issueTemplates, it)
  985. }
  986. }
  987. }
  988. return issueTemplates, invalidFiles
  989. }