Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package context
  6. import (
  7. "fmt"
  8. "io/ioutil"
  9. "net/url"
  10. "path"
  11. "strings"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/cache"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/log"
  16. "code.gitea.io/gitea/modules/markup/markdown"
  17. "code.gitea.io/gitea/modules/setting"
  18. api "code.gitea.io/gitea/modules/structs"
  19. "code.gitea.io/gitea/modules/util"
  20. "gitea.com/macaron/macaron"
  21. "github.com/editorconfig/editorconfig-core-go/v2"
  22. "github.com/unknwon/com"
  23. )
  24. // IssueTemplateDirCandidates issue templates directory
  25. var IssueTemplateDirCandidates = []string{
  26. "ISSUE_TEMPLATE",
  27. "issue_template",
  28. ".gitea/ISSUE_TEMPLATE",
  29. ".gitea/issue_template",
  30. ".github/ISSUE_TEMPLATE",
  31. ".github/issue_template",
  32. ".gitlab/ISSUE_TEMPLATE",
  33. ".gitlab/issue_template",
  34. }
  35. // PullRequest contains informations to make a pull request
  36. type PullRequest struct {
  37. BaseRepo *models.Repository
  38. Allowed bool
  39. SameRepo bool
  40. HeadInfo string // [<user>:]<branch>
  41. }
  42. // Repository contains information to operate a repository
  43. type Repository struct {
  44. models.Permission
  45. IsWatching bool
  46. IsViewBranch bool
  47. IsViewTag bool
  48. IsViewCommit bool
  49. Repository *models.Repository
  50. Owner *models.User
  51. Commit *git.Commit
  52. Tag *git.Tag
  53. GitRepo *git.Repository
  54. BranchName string
  55. TagName string
  56. TreePath string
  57. CommitID string
  58. RepoLink string
  59. CloneLink models.CloneLink
  60. CommitsCount int64
  61. Mirror *models.Mirror
  62. PullRequest *PullRequest
  63. }
  64. // CanEnableEditor returns true if repository is editable and user has proper access level.
  65. func (r *Repository) CanEnableEditor() bool {
  66. return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch && !r.Repository.IsArchived
  67. }
  68. // CanCreateBranch returns true if repository is editable and user has proper access level.
  69. func (r *Repository) CanCreateBranch() bool {
  70. return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanCreateBranch()
  71. }
  72. // RepoMustNotBeArchived checks if a repo is archived
  73. func RepoMustNotBeArchived() macaron.Handler {
  74. return func(ctx *Context) {
  75. if ctx.Repo.Repository.IsArchived {
  76. ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
  77. }
  78. }
  79. }
  80. // CanCommitToBranchResults represents the results of CanCommitToBranch
  81. type CanCommitToBranchResults struct {
  82. CanCommitToBranch bool
  83. EditorEnabled bool
  84. UserCanPush bool
  85. RequireSigned bool
  86. WillSign bool
  87. SigningKey string
  88. WontSignReason string
  89. }
  90. // CanCommitToBranch returns true if repository is editable and user has proper access level
  91. // and branch is not protected for push
  92. func (r *Repository) CanCommitToBranch(doer *models.User) (CanCommitToBranchResults, error) {
  93. protectedBranch, err := models.GetProtectedBranchBy(r.Repository.ID, r.BranchName)
  94. if err != nil {
  95. return CanCommitToBranchResults{}, err
  96. }
  97. userCanPush := true
  98. requireSigned := false
  99. if protectedBranch != nil {
  100. userCanPush = protectedBranch.CanUserPush(doer.ID)
  101. requireSigned = protectedBranch.RequireSignedCommits
  102. }
  103. sign, keyID, _, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
  104. canCommit := r.CanEnableEditor() && userCanPush
  105. if requireSigned {
  106. canCommit = canCommit && sign
  107. }
  108. wontSignReason := ""
  109. if err != nil {
  110. if models.IsErrWontSign(err) {
  111. wontSignReason = string(err.(*models.ErrWontSign).Reason)
  112. err = nil
  113. } else {
  114. wontSignReason = "error"
  115. }
  116. }
  117. return CanCommitToBranchResults{
  118. CanCommitToBranch: canCommit,
  119. EditorEnabled: r.CanEnableEditor(),
  120. UserCanPush: userCanPush,
  121. RequireSigned: requireSigned,
  122. WillSign: sign,
  123. SigningKey: keyID,
  124. WontSignReason: wontSignReason,
  125. }, err
  126. }
  127. // CanUseTimetracker returns whether or not a user can use the timetracker.
  128. func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) bool {
  129. // Checking for following:
  130. // 1. Is timetracker enabled
  131. // 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
  132. isAssigned, _ := models.IsUserAssignedToIssue(issue, user)
  133. return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() ||
  134. r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
  135. }
  136. // CanCreateIssueDependencies returns whether or not a user can create dependencies.
  137. func (r *Repository) CanCreateIssueDependencies(user *models.User, isPull bool) bool {
  138. return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
  139. }
  140. // GetCommitsCount returns cached commit count for current view
  141. func (r *Repository) GetCommitsCount() (int64, error) {
  142. var contextName string
  143. if r.IsViewBranch {
  144. contextName = r.BranchName
  145. } else if r.IsViewTag {
  146. contextName = r.TagName
  147. } else {
  148. contextName = r.CommitID
  149. }
  150. return cache.GetInt64(r.Repository.GetCommitsCountCacheKey(contextName, r.IsViewBranch || r.IsViewTag), func() (int64, error) {
  151. return r.Commit.CommitsCount()
  152. })
  153. }
  154. // GetCommitGraphsCount returns cached commit count for current view
  155. func (r *Repository) GetCommitGraphsCount(hidePRRefs bool, branches []string, files []string) (int64, error) {
  156. cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
  157. return cache.GetInt64(cacheKey, func() (int64, error) {
  158. if len(branches) == 0 {
  159. return git.AllCommitsCount(r.Repository.RepoPath(), hidePRRefs, files...)
  160. }
  161. return git.CommitsCountFiles(r.Repository.RepoPath(), branches, files)
  162. })
  163. }
  164. // BranchNameSubURL sub-URL for the BranchName field
  165. func (r *Repository) BranchNameSubURL() string {
  166. switch {
  167. case r.IsViewBranch:
  168. return "branch/" + r.BranchName
  169. case r.IsViewTag:
  170. return "tag/" + r.BranchName
  171. case r.IsViewCommit:
  172. return "commit/" + r.BranchName
  173. }
  174. log.Error("Unknown view type for repo: %v", r)
  175. return ""
  176. }
  177. // FileExists returns true if a file exists in the given repo branch
  178. func (r *Repository) FileExists(path string, branch string) (bool, error) {
  179. if branch == "" {
  180. branch = r.Repository.DefaultBranch
  181. }
  182. commit, err := r.GitRepo.GetBranchCommit(branch)
  183. if err != nil {
  184. return false, err
  185. }
  186. if _, err := commit.GetTreeEntryByPath(path); err != nil {
  187. return false, err
  188. }
  189. return true, nil
  190. }
  191. // GetEditorconfig returns the .editorconfig definition if found in the
  192. // HEAD of the default repo branch.
  193. func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
  194. if r.GitRepo == nil {
  195. return nil, nil
  196. }
  197. commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
  198. if err != nil {
  199. return nil, err
  200. }
  201. treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
  202. if err != nil {
  203. return nil, err
  204. }
  205. if treeEntry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
  206. return nil, git.ErrNotExist{ID: "", RelPath: ".editorconfig"}
  207. }
  208. reader, err := treeEntry.Blob().DataAsync()
  209. if err != nil {
  210. return nil, err
  211. }
  212. defer reader.Close()
  213. return editorconfig.Parse(reader)
  214. }
  215. // RetrieveBaseRepo retrieves base repository
  216. func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
  217. // Non-fork repository will not return error in this method.
  218. if err := repo.GetBaseRepo(); err != nil {
  219. if models.IsErrRepoNotExist(err) {
  220. repo.IsFork = false
  221. repo.ForkID = 0
  222. return
  223. }
  224. ctx.ServerError("GetBaseRepo", err)
  225. return
  226. } else if err = repo.BaseRepo.GetOwner(); err != nil {
  227. ctx.ServerError("BaseRepo.GetOwner", err)
  228. return
  229. }
  230. }
  231. // RetrieveTemplateRepo retrieves template repository used to generate this repository
  232. func RetrieveTemplateRepo(ctx *Context, repo *models.Repository) {
  233. // Non-generated repository will not return error in this method.
  234. if err := repo.GetTemplateRepo(); err != nil {
  235. if models.IsErrRepoNotExist(err) {
  236. repo.TemplateID = 0
  237. return
  238. }
  239. ctx.ServerError("GetTemplateRepo", err)
  240. return
  241. } else if err = repo.TemplateRepo.GetOwner(); err != nil {
  242. ctx.ServerError("TemplateRepo.GetOwner", err)
  243. return
  244. }
  245. perm, err := models.GetUserRepoPermission(repo.TemplateRepo, ctx.User)
  246. if err != nil {
  247. ctx.ServerError("GetUserRepoPermission", err)
  248. return
  249. }
  250. if !perm.CanRead(models.UnitTypeCode) {
  251. repo.TemplateID = 0
  252. }
  253. }
  254. // ComposeGoGetImport returns go-get-import meta content.
  255. func ComposeGoGetImport(owner, repo string) string {
  256. /// setting.AppUrl is guaranteed to be parse as url
  257. appURL, _ := url.Parse(setting.AppURL)
  258. return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
  259. }
  260. // EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
  261. // if user does not have actual access to the requested repository,
  262. // or the owner or repository does not exist at all.
  263. // This is particular a workaround for "go get" command which does not respect
  264. // .netrc file.
  265. func EarlyResponseForGoGetMeta(ctx *Context) {
  266. username := ctx.Params(":username")
  267. reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
  268. if username == "" || reponame == "" {
  269. ctx.PlainText(400, []byte("invalid repository path"))
  270. return
  271. }
  272. ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
  273. map[string]string{
  274. "GoGetImport": ComposeGoGetImport(username, reponame),
  275. "CloneLink": models.ComposeHTTPSCloneURL(username, reponame),
  276. })))
  277. }
  278. // RedirectToRepo redirect to a differently-named repository
  279. func RedirectToRepo(ctx *Context, redirectRepoID int64) {
  280. ownerName := ctx.Params(":username")
  281. previousRepoName := ctx.Params(":reponame")
  282. repo, err := models.GetRepositoryByID(redirectRepoID)
  283. if err != nil {
  284. ctx.ServerError("GetRepositoryByID", err)
  285. return
  286. }
  287. redirectPath := strings.Replace(
  288. ctx.Req.URL.Path,
  289. fmt.Sprintf("%s/%s", ownerName, previousRepoName),
  290. repo.FullName(),
  291. 1,
  292. )
  293. if ctx.Req.URL.RawQuery != "" {
  294. redirectPath += "?" + ctx.Req.URL.RawQuery
  295. }
  296. ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
  297. }
  298. func repoAssignment(ctx *Context, repo *models.Repository) {
  299. var err error
  300. if err = repo.GetOwner(); err != nil {
  301. ctx.ServerError("GetOwner", err)
  302. return
  303. }
  304. ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User)
  305. if err != nil {
  306. ctx.ServerError("GetUserRepoPermission", err)
  307. return
  308. }
  309. // Check access.
  310. if ctx.Repo.Permission.AccessMode == models.AccessModeNone {
  311. if ctx.Query("go-get") == "1" {
  312. EarlyResponseForGoGetMeta(ctx)
  313. return
  314. }
  315. ctx.NotFound("no access right", nil)
  316. return
  317. }
  318. ctx.Data["HasAccess"] = true
  319. ctx.Data["Permission"] = &ctx.Repo.Permission
  320. if repo.IsMirror {
  321. var err error
  322. ctx.Repo.Mirror, err = models.GetMirrorByRepoID(repo.ID)
  323. if err != nil {
  324. ctx.ServerError("GetMirror", err)
  325. return
  326. }
  327. ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
  328. ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
  329. ctx.Data["Mirror"] = ctx.Repo.Mirror
  330. }
  331. ctx.Repo.Repository = repo
  332. ctx.Data["RepoName"] = ctx.Repo.Repository.Name
  333. ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
  334. }
  335. // RepoIDAssignment returns a macaron handler which assigns the repo to the context.
  336. func RepoIDAssignment() macaron.Handler {
  337. return func(ctx *Context) {
  338. repoID := ctx.ParamsInt64(":repoid")
  339. // Get repository.
  340. repo, err := models.GetRepositoryByID(repoID)
  341. if err != nil {
  342. if models.IsErrRepoNotExist(err) {
  343. ctx.NotFound("GetRepositoryByID", nil)
  344. } else {
  345. ctx.ServerError("GetRepositoryByID", err)
  346. }
  347. return
  348. }
  349. repoAssignment(ctx, repo)
  350. }
  351. }
  352. // RepoAssignment returns a macaron to handle repository assignment
  353. func RepoAssignment() macaron.Handler {
  354. return func(ctx *Context) {
  355. var (
  356. owner *models.User
  357. err error
  358. )
  359. userName := ctx.Params(":username")
  360. repoName := ctx.Params(":reponame")
  361. // Check if the user is the same as the repository owner
  362. if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
  363. owner = ctx.User
  364. } else {
  365. owner, err = models.GetUserByName(userName)
  366. if err != nil {
  367. if models.IsErrUserNotExist(err) {
  368. redirectUserID, err := models.LookupUserRedirect(userName)
  369. if err == nil {
  370. RedirectToUser(ctx, userName, redirectUserID)
  371. } else if models.IsErrUserRedirectNotExist(err) {
  372. if ctx.Query("go-get") == "1" {
  373. EarlyResponseForGoGetMeta(ctx)
  374. return
  375. }
  376. ctx.NotFound("GetUserByName", nil)
  377. } else {
  378. ctx.ServerError("LookupUserRedirect", err)
  379. }
  380. } else {
  381. ctx.ServerError("GetUserByName", err)
  382. }
  383. return
  384. }
  385. }
  386. ctx.Repo.Owner = owner
  387. ctx.Data["Username"] = ctx.Repo.Owner.Name
  388. // Get repository.
  389. repo, err := models.GetRepositoryByName(owner.ID, repoName)
  390. if err != nil {
  391. if models.IsErrRepoNotExist(err) {
  392. redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
  393. if err == nil {
  394. RedirectToRepo(ctx, redirectRepoID)
  395. } else if models.IsErrRepoRedirectNotExist(err) {
  396. if ctx.Query("go-get") == "1" {
  397. EarlyResponseForGoGetMeta(ctx)
  398. return
  399. }
  400. ctx.NotFound("GetRepositoryByName", nil)
  401. } else {
  402. ctx.ServerError("LookupRepoRedirect", err)
  403. }
  404. } else {
  405. ctx.ServerError("GetRepositoryByName", err)
  406. }
  407. return
  408. }
  409. repo.Owner = owner
  410. repoAssignment(ctx, repo)
  411. if ctx.Written() {
  412. return
  413. }
  414. ctx.Repo.RepoLink = repo.Link()
  415. ctx.Data["RepoLink"] = ctx.Repo.RepoLink
  416. ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  417. unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
  418. if err == nil {
  419. ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
  420. }
  421. ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
  422. IncludeTags: true,
  423. })
  424. if err != nil {
  425. ctx.ServerError("GetReleaseCountByRepoID", err)
  426. return
  427. }
  428. ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
  429. if err != nil {
  430. ctx.ServerError("GetReleaseCountByRepoID", err)
  431. return
  432. }
  433. ctx.Data["Title"] = owner.Name + "/" + repo.Name
  434. ctx.Data["Repository"] = repo
  435. ctx.Data["Owner"] = ctx.Repo.Repository.Owner
  436. ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
  437. ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
  438. ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
  439. ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
  440. ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
  441. ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
  442. if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
  443. ctx.ServerError("CanUserFork", err)
  444. return
  445. }
  446. ctx.Data["DisableSSH"] = setting.SSH.Disabled
  447. ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
  448. ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
  449. ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
  450. ctx.Data["CloneLink"] = repo.CloneLink()
  451. ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
  452. if ctx.IsSigned {
  453. ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
  454. ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
  455. }
  456. if repo.IsFork {
  457. RetrieveBaseRepo(ctx, repo)
  458. if ctx.Written() {
  459. return
  460. }
  461. }
  462. if repo.IsGenerated() {
  463. RetrieveTemplateRepo(ctx, repo)
  464. if ctx.Written() {
  465. return
  466. }
  467. }
  468. // Disable everything when the repo is being created
  469. if ctx.Repo.Repository.IsBeingCreated() {
  470. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  471. return
  472. }
  473. gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
  474. if err != nil {
  475. ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
  476. return
  477. }
  478. ctx.Repo.GitRepo = gitRepo
  479. // We opened it, we should close it
  480. defer func() {
  481. // If it's been set to nil then assume someone else has closed it.
  482. if ctx.Repo.GitRepo != nil {
  483. ctx.Repo.GitRepo.Close()
  484. }
  485. }()
  486. // Stop at this point when the repo is empty.
  487. if ctx.Repo.Repository.IsEmpty {
  488. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  489. ctx.Next()
  490. return
  491. }
  492. tags, err := ctx.Repo.GitRepo.GetTags()
  493. if err != nil {
  494. ctx.ServerError("GetTags", err)
  495. return
  496. }
  497. ctx.Data["Tags"] = tags
  498. brs, err := ctx.Repo.GitRepo.GetBranches()
  499. if err != nil {
  500. ctx.ServerError("GetBranches", err)
  501. return
  502. }
  503. ctx.Data["Branches"] = brs
  504. ctx.Data["BranchesCount"] = len(brs)
  505. ctx.Data["TagName"] = ctx.Repo.TagName
  506. // If not branch selected, try default one.
  507. // If default branch doesn't exists, fall back to some other branch.
  508. if len(ctx.Repo.BranchName) == 0 {
  509. if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
  510. ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
  511. } else if len(brs) > 0 {
  512. ctx.Repo.BranchName = brs[0]
  513. }
  514. }
  515. ctx.Data["BranchName"] = ctx.Repo.BranchName
  516. ctx.Data["CommitID"] = ctx.Repo.CommitID
  517. // People who have push access or have forked repository can propose a new pull request.
  518. canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
  519. canCompare := false
  520. // Pull request is allowed if this is a fork repository
  521. // and base repository accepts pull requests.
  522. if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
  523. canCompare = true
  524. ctx.Data["BaseRepo"] = repo.BaseRepo
  525. ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
  526. ctx.Repo.PullRequest.Allowed = canPush
  527. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
  528. } else if repo.AllowsPulls() {
  529. // Or, this is repository accepts pull requests between branches.
  530. canCompare = true
  531. ctx.Data["BaseRepo"] = repo
  532. ctx.Repo.PullRequest.BaseRepo = repo
  533. ctx.Repo.PullRequest.Allowed = canPush
  534. ctx.Repo.PullRequest.SameRepo = true
  535. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
  536. }
  537. ctx.Data["CanCompareOrPull"] = canCompare
  538. ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
  539. if ctx.Query("go-get") == "1" {
  540. ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
  541. prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
  542. ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
  543. ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
  544. }
  545. ctx.Next()
  546. }
  547. }
  548. // RepoRefType type of repo reference
  549. type RepoRefType int
  550. const (
  551. // RepoRefLegacy unknown type, make educated guess and redirect.
  552. // for backward compatibility with previous URL scheme
  553. RepoRefLegacy RepoRefType = iota
  554. // RepoRefAny is for usage where educated guess is needed
  555. // but redirect can not be made
  556. RepoRefAny
  557. // RepoRefBranch branch
  558. RepoRefBranch
  559. // RepoRefTag tag
  560. RepoRefTag
  561. // RepoRefCommit commit
  562. RepoRefCommit
  563. // RepoRefBlob blob
  564. RepoRefBlob
  565. )
  566. // RepoRef handles repository reference names when the ref name is not
  567. // explicitly given
  568. func RepoRef() macaron.Handler {
  569. // since no ref name is explicitly specified, ok to just use branch
  570. return RepoRefByType(RepoRefBranch)
  571. }
  572. // RefTypeIncludesBranches returns true if ref type can be a branch
  573. func (rt RepoRefType) RefTypeIncludesBranches() bool {
  574. if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch {
  575. return true
  576. }
  577. return false
  578. }
  579. // RefTypeIncludesTags returns true if ref type can be a tag
  580. func (rt RepoRefType) RefTypeIncludesTags() bool {
  581. if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag {
  582. return true
  583. }
  584. return false
  585. }
  586. func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string {
  587. refName := ""
  588. parts := strings.Split(path, "/")
  589. for i, part := range parts {
  590. refName = strings.TrimPrefix(refName+"/"+part, "/")
  591. if isExist(refName) {
  592. ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
  593. return refName
  594. }
  595. }
  596. return ""
  597. }
  598. func getRefName(ctx *Context, pathType RepoRefType) string {
  599. path := ctx.Params("*")
  600. switch pathType {
  601. case RepoRefLegacy, RepoRefAny:
  602. if refName := getRefName(ctx, RepoRefBranch); len(refName) > 0 {
  603. return refName
  604. }
  605. if refName := getRefName(ctx, RepoRefTag); len(refName) > 0 {
  606. return refName
  607. }
  608. // For legacy and API support only full commit sha
  609. parts := strings.Split(path, "/")
  610. if len(parts) > 0 && len(parts[0]) == 40 {
  611. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  612. return parts[0]
  613. }
  614. if refName := getRefName(ctx, RepoRefBlob); len(refName) > 0 {
  615. return refName
  616. }
  617. ctx.Repo.TreePath = path
  618. return ctx.Repo.Repository.DefaultBranch
  619. case RepoRefBranch:
  620. return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist)
  621. case RepoRefTag:
  622. return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist)
  623. case RepoRefCommit:
  624. parts := strings.Split(path, "/")
  625. if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= 40 {
  626. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  627. return parts[0]
  628. }
  629. case RepoRefBlob:
  630. _, err := ctx.Repo.GitRepo.GetBlob(path)
  631. if err != nil {
  632. return ""
  633. }
  634. return path
  635. default:
  636. log.Error("Unrecognized path type: %v", path)
  637. }
  638. return ""
  639. }
  640. // RepoRefByType handles repository reference name for a specific type
  641. // of repository reference
  642. func RepoRefByType(refType RepoRefType) macaron.Handler {
  643. return func(ctx *Context) {
  644. // Empty repository does not have reference information.
  645. if ctx.Repo.Repository.IsEmpty {
  646. return
  647. }
  648. var (
  649. refName string
  650. err error
  651. )
  652. if ctx.Repo.GitRepo == nil {
  653. repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  654. ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
  655. if err != nil {
  656. ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
  657. return
  658. }
  659. // We opened it, we should close it
  660. defer func() {
  661. // If it's been set to nil then assume someone else has closed it.
  662. if ctx.Repo.GitRepo != nil {
  663. ctx.Repo.GitRepo.Close()
  664. }
  665. }()
  666. }
  667. // Get default branch.
  668. if len(ctx.Params("*")) == 0 {
  669. refName = ctx.Repo.Repository.DefaultBranch
  670. ctx.Repo.BranchName = refName
  671. if !ctx.Repo.GitRepo.IsBranchExist(refName) {
  672. brs, err := ctx.Repo.GitRepo.GetBranches()
  673. if err != nil {
  674. ctx.ServerError("GetBranches", err)
  675. return
  676. } else if len(brs) == 0 {
  677. err = fmt.Errorf("No branches in non-empty repository %s",
  678. ctx.Repo.GitRepo.Path)
  679. ctx.ServerError("GetBranches", err)
  680. return
  681. }
  682. refName = brs[0]
  683. }
  684. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  685. if err != nil {
  686. ctx.ServerError("GetBranchCommit", err)
  687. return
  688. }
  689. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  690. ctx.Repo.IsViewBranch = true
  691. } else {
  692. refName = getRefName(ctx, refType)
  693. ctx.Repo.BranchName = refName
  694. if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
  695. ctx.Repo.IsViewBranch = true
  696. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  697. if err != nil {
  698. ctx.ServerError("GetBranchCommit", err)
  699. return
  700. }
  701. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  702. } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
  703. ctx.Repo.IsViewTag = true
  704. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
  705. if err != nil {
  706. ctx.ServerError("GetTagCommit", err)
  707. return
  708. }
  709. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  710. } else if len(refName) >= 7 && len(refName) <= 40 {
  711. ctx.Repo.IsViewCommit = true
  712. ctx.Repo.CommitID = refName
  713. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
  714. if err != nil {
  715. ctx.NotFound("GetCommit", err)
  716. return
  717. }
  718. // If short commit ID add canonical link header
  719. if len(refName) < 40 {
  720. ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
  721. util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
  722. }
  723. } else {
  724. ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
  725. return
  726. }
  727. if refType == RepoRefLegacy {
  728. // redirect from old URL scheme to new URL scheme
  729. ctx.Redirect(path.Join(
  730. setting.AppSubURL,
  731. strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
  732. ctx.Repo.BranchNameSubURL(),
  733. ctx.Repo.TreePath))
  734. return
  735. }
  736. }
  737. ctx.Data["BranchName"] = ctx.Repo.BranchName
  738. ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
  739. ctx.Data["CommitID"] = ctx.Repo.CommitID
  740. ctx.Data["TreePath"] = ctx.Repo.TreePath
  741. ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
  742. ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
  743. ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
  744. ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
  745. ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
  746. if err != nil {
  747. ctx.ServerError("GetCommitsCount", err)
  748. return
  749. }
  750. ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
  751. ctx.Next()
  752. }
  753. }
  754. // GitHookService checks if repository Git hooks service has been enabled.
  755. func GitHookService() macaron.Handler {
  756. return func(ctx *Context) {
  757. if !ctx.User.CanEditGitHook() {
  758. ctx.NotFound("GitHookService", nil)
  759. return
  760. }
  761. }
  762. }
  763. // UnitTypes returns a macaron middleware to set unit types to context variables.
  764. func UnitTypes() macaron.Handler {
  765. return func(ctx *Context) {
  766. ctx.Data["UnitTypeCode"] = models.UnitTypeCode
  767. ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues
  768. ctx.Data["UnitTypePullRequests"] = models.UnitTypePullRequests
  769. ctx.Data["UnitTypeReleases"] = models.UnitTypeReleases
  770. ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki
  771. ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki
  772. ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker
  773. ctx.Data["UnitTypeProjects"] = models.UnitTypeProjects
  774. }
  775. }
  776. // IssueTemplatesFromDefaultBranch checks for issue templates in the repo's default branch
  777. func (ctx *Context) IssueTemplatesFromDefaultBranch() []api.IssueTemplate {
  778. var issueTemplates []api.IssueTemplate
  779. if ctx.Repo.Commit == nil {
  780. var err error
  781. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  782. if err != nil {
  783. return issueTemplates
  784. }
  785. }
  786. for _, dirName := range IssueTemplateDirCandidates {
  787. tree, err := ctx.Repo.Commit.SubTree(dirName)
  788. if err != nil {
  789. continue
  790. }
  791. entries, err := tree.ListEntries()
  792. if err != nil {
  793. return issueTemplates
  794. }
  795. for _, entry := range entries {
  796. if strings.HasSuffix(entry.Name(), ".md") {
  797. if entry.Blob().Size() >= setting.UI.MaxDisplayFileSize {
  798. log.Debug("Issue template is too large: %s", entry.Name())
  799. continue
  800. }
  801. r, err := entry.Blob().DataAsync()
  802. if err != nil {
  803. log.Debug("DataAsync: %v", err)
  804. continue
  805. }
  806. defer r.Close()
  807. data, err := ioutil.ReadAll(r)
  808. if err != nil {
  809. log.Debug("ReadAll: %v", err)
  810. continue
  811. }
  812. var it api.IssueTemplate
  813. content, err := markdown.ExtractMetadata(string(data), &it)
  814. if err != nil {
  815. log.Debug("ExtractMetadata: %v", err)
  816. continue
  817. }
  818. it.Content = content
  819. it.FileName = entry.Name()
  820. if it.Valid() {
  821. issueTemplates = append(issueTemplates, it)
  822. }
  823. }
  824. }
  825. if len(issueTemplates) > 0 {
  826. return issueTemplates
  827. }
  828. }
  829. return issueTemplates
  830. }