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 32KB

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