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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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. type PullRequest struct {
  19. BaseRepo *models.Repository
  20. Allowed bool
  21. SameRepo bool
  22. HeadInfo string // [<user>:]<branch>
  23. }
  24. type Repository struct {
  25. AccessMode models.AccessMode
  26. IsWatching bool
  27. IsViewBranch bool
  28. IsViewTag bool
  29. IsViewCommit bool
  30. Repository *models.Repository
  31. Owner *models.User
  32. Commit *git.Commit
  33. Tag *git.Tag
  34. GitRepo *git.Repository
  35. BranchName string
  36. TagName string
  37. TreePath string
  38. CommitID string
  39. RepoLink string
  40. CloneLink models.CloneLink
  41. CommitsCount int64
  42. Mirror *models.Mirror
  43. PullRequest *PullRequest
  44. }
  45. // IsOwner returns true if current user is the owner of repository.
  46. func (r *Repository) IsOwner() bool {
  47. return r.AccessMode >= models.AccessModeOwner
  48. }
  49. // IsAdmin returns true if current user has admin or higher access of repository.
  50. func (r *Repository) IsAdmin() bool {
  51. return r.AccessMode >= models.AccessModeAdmin
  52. }
  53. // IsWriter returns true if current user has write or higher access of repository.
  54. func (r *Repository) IsWriter() bool {
  55. return r.AccessMode >= models.AccessModeWrite
  56. }
  57. // HasAccess returns true if the current user has at least read access for this repository
  58. func (r *Repository) HasAccess() bool {
  59. return r.AccessMode >= models.AccessModeRead
  60. }
  61. // CanEnableEditor returns true if repository is editable and user has proper access level.
  62. func (r *Repository) CanEnableEditor() bool {
  63. return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter()
  64. }
  65. // GetEditorconfig returns the .editorconfig definition if found in the
  66. // HEAD of the default repo branch.
  67. func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
  68. commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
  69. if err != nil {
  70. return nil, err
  71. }
  72. treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
  73. if err != nil {
  74. return nil, err
  75. }
  76. reader, err := treeEntry.Blob().Data()
  77. if err != nil {
  78. return nil, err
  79. }
  80. data, err := ioutil.ReadAll(reader)
  81. if err != nil {
  82. return nil, err
  83. }
  84. return editorconfig.ParseBytes(data)
  85. }
  86. func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
  87. // Non-fork repository will not return error in this method.
  88. if err := repo.GetBaseRepo(); err != nil {
  89. if models.IsErrRepoNotExist(err) {
  90. repo.IsFork = false
  91. repo.ForkID = 0
  92. return
  93. }
  94. ctx.Handle(500, "GetBaseRepo", err)
  95. return
  96. } else if err = repo.BaseRepo.GetOwner(); err != nil {
  97. ctx.Handle(500, "BaseRepo.GetOwner", err)
  98. return
  99. }
  100. }
  101. // composeGoGetImport returns go-get-import meta content.
  102. func composeGoGetImport(owner, repo string) string {
  103. return path.Join(setting.Domain, setting.AppSubUrl, owner, repo)
  104. }
  105. // earlyResponseForGoGetMeta responses appropriate go-get meta with status 200
  106. // if user does not have actual access to the requested repository,
  107. // or the owner or repository does not exist at all.
  108. // This is particular a workaround for "go get" command which does not respect
  109. // .netrc file.
  110. func earlyResponseForGoGetMeta(ctx *Context) {
  111. ctx.PlainText(200, []byte(com.Expand(`<meta name="go-import" content="{GoGetImport} git {CloneLink}">`,
  112. map[string]string{
  113. "GoGetImport": composeGoGetImport(ctx.Params(":username"), ctx.Params(":reponame")),
  114. "CloneLink": models.ComposeHTTPSCloneURL(ctx.Params(":username"), ctx.Params(":reponame")),
  115. })))
  116. }
  117. func RepoAssignment(args ...bool) macaron.Handler {
  118. return func(ctx *Context) {
  119. var (
  120. displayBare bool // To display bare page if it is a bare repo.
  121. )
  122. if len(args) >= 1 {
  123. displayBare = args[0]
  124. }
  125. var (
  126. owner *models.User
  127. err error
  128. )
  129. userName := ctx.Params(":username")
  130. repoName := ctx.Params(":reponame")
  131. refName := ctx.Params(":branchname")
  132. if len(refName) == 0 {
  133. refName = ctx.Params(":path")
  134. }
  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. func RequireRepoAdmin() macaron.Handler {
  406. return func(ctx *Context) {
  407. if !ctx.IsSigned || (!ctx.Repo.IsAdmin() && !ctx.User.IsAdmin) {
  408. ctx.Handle(404, ctx.Req.RequestURI, nil)
  409. return
  410. }
  411. }
  412. }
  413. func RequireRepoWriter() macaron.Handler {
  414. return func(ctx *Context) {
  415. if !ctx.IsSigned || (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) {
  416. ctx.Handle(404, ctx.Req.RequestURI, nil)
  417. return
  418. }
  419. }
  420. }
  421. // GitHookService checks if repository Git hooks service has been enabled.
  422. func GitHookService() macaron.Handler {
  423. return func(ctx *Context) {
  424. if !ctx.User.CanEditGitHook() {
  425. ctx.Handle(404, "GitHookService", nil)
  426. return
  427. }
  428. }
  429. }