You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo.go 16 kB

11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package context
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "path"
  9. "strings"
  10. "code.gitea.io/git"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/setting"
  13. "github.com/Unknwon/com"
  14. editorconfig "gopkg.in/editorconfig/editorconfig-core-go.v1"
  15. macaron "gopkg.in/macaron.v1"
  16. )
  17. // PullRequest contains informations to make a pull request
  18. type PullRequest struct {
  19. BaseRepo *models.Repository
  20. Allowed bool
  21. SameRepo bool
  22. HeadInfo string // [<user>:]<branch>
  23. }
  24. // Repository contains information to operate a repository
  25. type Repository struct {
  26. AccessMode models.AccessMode
  27. IsWatching bool
  28. IsViewBranch bool
  29. IsViewTag bool
  30. IsViewCommit bool
  31. Repository *models.Repository
  32. Owner *models.User
  33. Commit *git.Commit
  34. Tag *git.Tag
  35. GitRepo *git.Repository
  36. BranchName string
  37. TagName string
  38. TreePath string
  39. CommitID string
  40. RepoLink string
  41. CloneLink models.CloneLink
  42. CommitsCount int64
  43. Mirror *models.Mirror
  44. PullRequest *PullRequest
  45. }
  46. // IsOwner returns true if current user is the owner of repository.
  47. func (r *Repository) IsOwner() bool {
  48. return r.AccessMode >= models.AccessModeOwner
  49. }
  50. // IsAdmin returns true if current user has admin or higher access of repository.
  51. func (r *Repository) IsAdmin() bool {
  52. return r.AccessMode >= models.AccessModeAdmin
  53. }
  54. // IsWriter returns true if current user has write or higher access of repository.
  55. func (r *Repository) IsWriter() bool {
  56. return r.AccessMode >= models.AccessModeWrite
  57. }
  58. // HasAccess returns true if the current user has at least read access for this repository
  59. func (r *Repository) HasAccess() bool {
  60. return r.AccessMode >= models.AccessModeRead
  61. }
  62. // CanEnableEditor returns true if repository is editable and user has proper access level.
  63. func (r *Repository) CanEnableEditor() bool {
  64. return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter()
  65. }
  66. // CanCommitToBranch returns true if repository is editable and user has proper access level
  67. // and branch is not protected
  68. func (r *Repository) CanCommitToBranch() (bool, error) {
  69. protectedBranch, err := r.Repository.IsProtectedBranch(r.BranchName)
  70. if err != nil {
  71. return false, err
  72. }
  73. return r.CanEnableEditor() && !protectedBranch, nil
  74. }
  75. // GetEditorconfig returns the .editorconfig definition if found in the
  76. // HEAD of the default repo branch.
  77. func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
  78. commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
  79. if err != nil {
  80. return nil, err
  81. }
  82. treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
  83. if err != nil {
  84. return nil, err
  85. }
  86. reader, err := treeEntry.Blob().Data()
  87. if err != nil {
  88. return nil, err
  89. }
  90. data, err := ioutil.ReadAll(reader)
  91. if err != nil {
  92. return nil, err
  93. }
  94. return editorconfig.ParseBytes(data)
  95. }
  96. // RetrieveBaseRepo retrieves base repository
  97. func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
  98. // Non-fork repository will not return error in this method.
  99. if err := repo.GetBaseRepo(); err != nil {
  100. if models.IsErrRepoNotExist(err) {
  101. repo.IsFork = false
  102. repo.ForkID = 0
  103. return
  104. }
  105. ctx.Handle(500, "GetBaseRepo", err)
  106. return
  107. } else if err = repo.BaseRepo.GetOwner(); err != nil {
  108. ctx.Handle(500, "BaseRepo.GetOwner", err)
  109. return
  110. }
  111. }
  112. // composeGoGetImport returns go-get-import meta content.
  113. func composeGoGetImport(owner, repo string) string {
  114. return path.Join(setting.Domain, setting.AppSubURL, owner, repo)
  115. }
  116. // earlyResponseForGoGetMeta responses appropriate go-get meta with status 200
  117. // if user does not have actual access to the requested repository,
  118. // or the owner or repository does not exist at all.
  119. // This is particular a workaround for "go get" command which does not respect
  120. // .netrc file.
  121. func earlyResponseForGoGetMeta(ctx *Context) {
  122. ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
  123. map[string]string{
  124. "GoGetImport": composeGoGetImport(ctx.Params(":username"), strings.TrimSuffix(ctx.Params(":reponame"), ".git")),
  125. "CloneLink": models.ComposeHTTPSCloneURL(ctx.Params(":username"), ctx.Params(":reponame")),
  126. })))
  127. }
  128. // RedirectToRepo redirect to a differently-named repository
  129. func RedirectToRepo(ctx *Context, redirectRepoID int64) {
  130. ownerName := ctx.Params(":username")
  131. previousRepoName := ctx.Params(":reponame")
  132. repo, err := models.GetRepositoryByID(redirectRepoID)
  133. if err != nil {
  134. ctx.Handle(500, "GetRepositoryByID", err)
  135. return
  136. }
  137. redirectPath := strings.Replace(
  138. ctx.Req.URL.Path,
  139. fmt.Sprintf("%s/%s", ownerName, previousRepoName),
  140. fmt.Sprintf("%s/%s", ownerName, repo.Name),
  141. 1,
  142. )
  143. ctx.Redirect(redirectPath)
  144. }
  145. // RepoAssignment returns a macaron to handle repository assignment
  146. func RepoAssignment() macaron.Handler {
  147. return func(ctx *Context) {
  148. var (
  149. owner *models.User
  150. err error
  151. )
  152. userName := ctx.Params(":username")
  153. repoName := ctx.Params(":reponame")
  154. // Check if the user is the same as the repository owner
  155. if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
  156. owner = ctx.User
  157. } else {
  158. owner, err = models.GetUserByName(userName)
  159. if err != nil {
  160. if models.IsErrUserNotExist(err) {
  161. if ctx.Query("go-get") == "1" {
  162. earlyResponseForGoGetMeta(ctx)
  163. return
  164. }
  165. ctx.Handle(404, "GetUserByName", err)
  166. } else {
  167. ctx.Handle(500, "GetUserByName", err)
  168. }
  169. return
  170. }
  171. }
  172. ctx.Repo.Owner = owner
  173. ctx.Data["Username"] = ctx.Repo.Owner.Name
  174. // Get repository.
  175. repo, err := models.GetRepositoryByName(owner.ID, repoName)
  176. if err != nil {
  177. if models.IsErrRepoNotExist(err) {
  178. redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
  179. if err == nil {
  180. RedirectToRepo(ctx, redirectRepoID)
  181. } else if models.IsErrRepoRedirectNotExist(err) {
  182. if ctx.Query("go-get") == "1" {
  183. earlyResponseForGoGetMeta(ctx)
  184. return
  185. }
  186. ctx.Handle(404, "GetRepositoryByName", err)
  187. } else {
  188. ctx.Handle(500, "LookupRepoRedirect", err)
  189. }
  190. } else {
  191. ctx.Handle(500, "GetRepositoryByName", err)
  192. }
  193. return
  194. }
  195. repo.Owner = owner
  196. // Admin has super access.
  197. if ctx.IsSigned && ctx.User.IsAdmin {
  198. ctx.Repo.AccessMode = models.AccessModeOwner
  199. } else {
  200. var userID int64
  201. if ctx.User != nil {
  202. userID = ctx.User.ID
  203. }
  204. mode, err := models.AccessLevel(userID, repo)
  205. if err != nil {
  206. ctx.Handle(500, "AccessLevel", err)
  207. return
  208. }
  209. ctx.Repo.AccessMode = mode
  210. }
  211. // Check access.
  212. if ctx.Repo.AccessMode == models.AccessModeNone {
  213. if ctx.Query("go-get") == "1" {
  214. earlyResponseForGoGetMeta(ctx)
  215. return
  216. }
  217. ctx.Handle(404, "no access right", err)
  218. return
  219. }
  220. ctx.Data["HasAccess"] = true
  221. if repo.IsMirror {
  222. ctx.Repo.Mirror, err = models.GetMirrorByRepoID(repo.ID)
  223. if err != nil {
  224. ctx.Handle(500, "GetMirror", err)
  225. return
  226. }
  227. ctx.Data["MirrorEnablePrune"] = ctx.Repo.Mirror.EnablePrune
  228. ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
  229. ctx.Data["Mirror"] = ctx.Repo.Mirror
  230. }
  231. ctx.Repo.Repository = repo
  232. ctx.Data["RepoName"] = ctx.Repo.Repository.Name
  233. ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
  234. gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
  235. if err != nil {
  236. ctx.Handle(500, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
  237. return
  238. }
  239. ctx.Repo.GitRepo = gitRepo
  240. ctx.Repo.RepoLink = repo.Link()
  241. ctx.Data["RepoLink"] = ctx.Repo.RepoLink
  242. ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
  243. tags, err := ctx.Repo.GitRepo.GetTags()
  244. if err != nil {
  245. ctx.Handle(500, "GetTags", err)
  246. return
  247. }
  248. ctx.Data["Tags"] = tags
  249. ctx.Repo.Repository.NumTags = len(tags)
  250. ctx.Data["Title"] = owner.Name + "/" + repo.Name
  251. ctx.Data["Repository"] = repo
  252. ctx.Data["Owner"] = ctx.Repo.Repository.Owner
  253. ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
  254. ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
  255. ctx.Data["IsRepositoryWriter"] = ctx.Repo.IsWriter()
  256. ctx.Data["DisableSSH"] = setting.SSH.Disabled
  257. ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
  258. ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
  259. ctx.Data["CloneLink"] = repo.CloneLink()
  260. ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
  261. if ctx.IsSigned {
  262. ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
  263. ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
  264. }
  265. // repo is bare and display enable
  266. if ctx.Repo.Repository.IsBare {
  267. ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
  268. return
  269. }
  270. ctx.Data["TagName"] = ctx.Repo.TagName
  271. brs, err := ctx.Repo.GitRepo.GetBranches()
  272. if err != nil {
  273. ctx.Handle(500, "GetBranches", err)
  274. return
  275. }
  276. ctx.Data["Branches"] = brs
  277. ctx.Data["BrancheCount"] = len(brs)
  278. // If not branch selected, try default one.
  279. // If default branch doesn't exists, fall back to some other branch.
  280. if len(ctx.Repo.BranchName) == 0 {
  281. if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
  282. ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
  283. } else if len(brs) > 0 {
  284. ctx.Repo.BranchName = brs[0]
  285. }
  286. }
  287. ctx.Data["BranchName"] = ctx.Repo.BranchName
  288. ctx.Data["CommitID"] = ctx.Repo.CommitID
  289. if repo.IsFork {
  290. RetrieveBaseRepo(ctx, repo)
  291. if ctx.Written() {
  292. return
  293. }
  294. }
  295. // People who have push access or have forked repository can propose a new pull request.
  296. if ctx.Repo.IsWriter() || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
  297. // Pull request is allowed if this is a fork repository
  298. // and base repository accepts pull requests.
  299. if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
  300. ctx.Data["BaseRepo"] = repo.BaseRepo
  301. ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
  302. ctx.Repo.PullRequest.Allowed = true
  303. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
  304. } else {
  305. // Or, this is repository accepts pull requests between branches.
  306. if repo.AllowsPulls() {
  307. ctx.Data["BaseRepo"] = repo
  308. ctx.Repo.PullRequest.BaseRepo = repo
  309. ctx.Repo.PullRequest.Allowed = true
  310. ctx.Repo.PullRequest.SameRepo = true
  311. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
  312. }
  313. }
  314. }
  315. ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
  316. if ctx.Query("go-get") == "1" {
  317. ctx.Data["GoGetImport"] = composeGoGetImport(owner.Name, repo.Name)
  318. prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", ctx.Repo.BranchName)
  319. ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
  320. ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
  321. }
  322. }
  323. }
  324. // RepoRef handles repository reference name including those contain `/`.
  325. func RepoRef() macaron.Handler {
  326. return func(ctx *Context) {
  327. // Empty repository does not have reference information.
  328. if ctx.Repo.Repository.IsBare {
  329. return
  330. }
  331. var (
  332. refName string
  333. err error
  334. )
  335. // For API calls.
  336. if ctx.Repo.GitRepo == nil {
  337. repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  338. ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
  339. if err != nil {
  340. ctx.Handle(500, "RepoRef Invalid repo "+repoPath, err)
  341. return
  342. }
  343. }
  344. // Get default branch.
  345. if len(ctx.Params("*")) == 0 {
  346. refName = ctx.Repo.Repository.DefaultBranch
  347. if !ctx.Repo.GitRepo.IsBranchExist(refName) {
  348. brs, err := ctx.Repo.GitRepo.GetBranches()
  349. if err != nil {
  350. ctx.Handle(500, "GetBranches", err)
  351. return
  352. } else if len(brs) == 0 {
  353. err = fmt.Errorf("No branches in non-bare repository %s",
  354. ctx.Repo.GitRepo.Path)
  355. ctx.Handle(500, "GetBranches", err)
  356. return
  357. }
  358. refName = brs[0]
  359. }
  360. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  361. if err != nil {
  362. ctx.Handle(500, "GetBranchCommit", err)
  363. return
  364. }
  365. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  366. ctx.Repo.IsViewBranch = true
  367. } else {
  368. hasMatched := false
  369. parts := strings.Split(ctx.Params("*"), "/")
  370. for i, part := range parts {
  371. refName = strings.TrimPrefix(refName+"/"+part, "/")
  372. if ctx.Repo.GitRepo.IsBranchExist(refName) ||
  373. ctx.Repo.GitRepo.IsTagExist(refName) {
  374. if i < len(parts)-1 {
  375. ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
  376. }
  377. hasMatched = true
  378. break
  379. }
  380. }
  381. if !hasMatched && len(parts[0]) == 40 {
  382. refName = parts[0]
  383. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  384. }
  385. if ctx.Repo.GitRepo.IsBranchExist(refName) {
  386. ctx.Repo.IsViewBranch = true
  387. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  388. if err != nil {
  389. ctx.Handle(500, "GetBranchCommit", err)
  390. return
  391. }
  392. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  393. } else if ctx.Repo.GitRepo.IsTagExist(refName) {
  394. ctx.Repo.IsViewTag = true
  395. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
  396. if err != nil {
  397. ctx.Handle(500, "GetTagCommit", err)
  398. return
  399. }
  400. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  401. } else if len(refName) == 40 {
  402. ctx.Repo.IsViewCommit = true
  403. ctx.Repo.CommitID = refName
  404. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
  405. if err != nil {
  406. ctx.Handle(404, "GetCommit", nil)
  407. return
  408. }
  409. } else {
  410. ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
  411. return
  412. }
  413. }
  414. ctx.Repo.BranchName = refName
  415. ctx.Data["BranchName"] = ctx.Repo.BranchName
  416. ctx.Data["CommitID"] = ctx.Repo.CommitID
  417. ctx.Data["TreePath"] = ctx.Repo.TreePath
  418. ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
  419. ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
  420. ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
  421. ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
  422. if err != nil {
  423. ctx.Handle(500, "CommitsCount", err)
  424. return
  425. }
  426. ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
  427. }
  428. }
  429. // RequireRepoAdmin returns a macaron middleware for requiring repository admin permission
  430. func RequireRepoAdmin() macaron.Handler {
  431. return func(ctx *Context) {
  432. if !ctx.IsSigned || (!ctx.Repo.IsAdmin() && !ctx.User.IsAdmin) {
  433. ctx.Handle(404, ctx.Req.RequestURI, nil)
  434. return
  435. }
  436. }
  437. }
  438. // RequireRepoWriter returns a macaron middleware for requiring repository write permission
  439. func RequireRepoWriter() macaron.Handler {
  440. return func(ctx *Context) {
  441. if !ctx.IsSigned || (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) {
  442. ctx.Handle(404, ctx.Req.RequestURI, nil)
  443. return
  444. }
  445. }
  446. }
  447. // LoadRepoUnits loads repsitory's units, it should be called after repository and user loaded
  448. func LoadRepoUnits() macaron.Handler {
  449. return func(ctx *Context) {
  450. var isAdmin bool
  451. if ctx.User != nil && ctx.User.IsAdmin {
  452. isAdmin = true
  453. }
  454. var userID int64
  455. if ctx.User != nil {
  456. userID = ctx.User.ID
  457. }
  458. err := ctx.Repo.Repository.LoadUnitsByUserID(userID, isAdmin)
  459. if err != nil {
  460. ctx.Handle(500, "LoadUnitsByUserID", err)
  461. return
  462. }
  463. }
  464. }
  465. // CheckUnit will check whether
  466. func CheckUnit(unitType models.UnitType) macaron.Handler {
  467. return func(ctx *Context) {
  468. var find bool
  469. for _, unit := range ctx.Repo.Repository.Units {
  470. if unit.Type == unitType {
  471. find = true
  472. break
  473. }
  474. }
  475. if !find {
  476. ctx.Handle(404, "CheckUnit", fmt.Errorf("%s: %v", ctx.Tr("units.error.unit_not_allowed"), unitType))
  477. }
  478. }
  479. }
  480. // GitHookService checks if repository Git hooks service has been enabled.
  481. func GitHookService() macaron.Handler {
  482. return func(ctx *Context) {
  483. if !ctx.User.CanEditGitHook() {
  484. ctx.Handle(404, "GitHookService", nil)
  485. return
  486. }
  487. }
  488. }
  489. // UnitTypes returns a macaron middleware to set unit types to context variables.
  490. func UnitTypes() macaron.Handler {
  491. return func(ctx *Context) {
  492. ctx.Data["UnitTypeCode"] = models.UnitTypeCode
  493. ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues
  494. ctx.Data["UnitTypePullRequests"] = models.UnitTypePullRequests
  495. ctx.Data["UnitTypeCommits"] = models.UnitTypeCommits
  496. ctx.Data["UnitTypeReleases"] = models.UnitTypeReleases
  497. ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki
  498. ctx.Data["UnitTypeSettings"] = models.UnitTypeSettings
  499. ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki
  500. ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker
  501. }
  502. }