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