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

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