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

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