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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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. "github.com/Unknwon/com"
  11. "gopkg.in/editorconfig/editorconfig-core-go.v1"
  12. "gopkg.in/macaron.v1"
  13. "github.com/gogits/git-module"
  14. "github.com/go-gitea/gitea/models"
  15. "github.com/go-gitea/gitea/modules/log"
  16. "github.com/go-gitea/gitea/modules/setting"
  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.ACCESS_MODE_OWNER
  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.ACCESS_MODE_ADMIN
  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.ACCESS_MODE_WRITE
  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.ACCESS_MODE_READ
  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.ACCESS_MODE_OWNER
  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.ACCESS_MODE_NONE {
  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["CloneLink"] = repo.CloneLink()
  230. ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
  231. if ctx.IsSigned {
  232. ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
  233. ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
  234. }
  235. // repo is bare and display enable
  236. if ctx.Repo.Repository.IsBare {
  237. log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
  238. // NOTE: to prevent templating error
  239. ctx.Data["BranchName"] = ""
  240. if displayBare {
  241. if !ctx.Repo.IsAdmin() {
  242. ctx.Flash.Info(ctx.Tr("repo.repo_is_empty"), true)
  243. }
  244. ctx.HTML(200, "repo/bare")
  245. }
  246. return
  247. }
  248. ctx.Data["TagName"] = ctx.Repo.TagName
  249. brs, err := ctx.Repo.GitRepo.GetBranches()
  250. if err != nil {
  251. ctx.Handle(500, "GetBranches", err)
  252. return
  253. }
  254. ctx.Data["Branches"] = brs
  255. ctx.Data["BrancheCount"] = len(brs)
  256. // If not branch selected, try default one.
  257. // If default branch doesn't exists, fall back to some other branch.
  258. if len(ctx.Repo.BranchName) == 0 {
  259. if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
  260. ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
  261. } else if len(brs) > 0 {
  262. ctx.Repo.BranchName = brs[0]
  263. }
  264. }
  265. ctx.Data["BranchName"] = ctx.Repo.BranchName
  266. ctx.Data["CommitID"] = ctx.Repo.CommitID
  267. if repo.IsFork {
  268. RetrieveBaseRepo(ctx, repo)
  269. if ctx.Written() {
  270. return
  271. }
  272. }
  273. // People who have push access or have fored repository can propose a new pull request.
  274. if ctx.Repo.IsWriter() || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
  275. // Pull request is allowed if this is a fork repository
  276. // and base repository accepts pull requests.
  277. if repo.BaseRepo != nil {
  278. if repo.BaseRepo.AllowsPulls() {
  279. ctx.Data["BaseRepo"] = repo.BaseRepo
  280. ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
  281. ctx.Repo.PullRequest.Allowed = true
  282. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
  283. }
  284. } else {
  285. // Or, this is repository accepts pull requests between branches.
  286. if repo.AllowsPulls() {
  287. ctx.Data["BaseRepo"] = repo
  288. ctx.Repo.PullRequest.BaseRepo = repo
  289. ctx.Repo.PullRequest.Allowed = true
  290. ctx.Repo.PullRequest.SameRepo = true
  291. ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
  292. }
  293. }
  294. }
  295. ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
  296. if ctx.Query("go-get") == "1" {
  297. ctx.Data["GoGetImport"] = composeGoGetImport(owner.Name, repo.Name)
  298. prefix := setting.AppUrl + path.Join(owner.Name, repo.Name, "src", ctx.Repo.BranchName)
  299. ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
  300. ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
  301. }
  302. }
  303. }
  304. // RepoRef handles repository reference name including those contain `/`.
  305. func RepoRef() macaron.Handler {
  306. return func(ctx *Context) {
  307. // Empty repository does not have reference information.
  308. if ctx.Repo.Repository.IsBare {
  309. return
  310. }
  311. var (
  312. refName string
  313. err error
  314. )
  315. // For API calls.
  316. if ctx.Repo.GitRepo == nil {
  317. repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
  318. ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
  319. if err != nil {
  320. ctx.Handle(500, "RepoRef Invalid repo "+repoPath, err)
  321. return
  322. }
  323. }
  324. // Get default branch.
  325. if len(ctx.Params("*")) == 0 {
  326. refName = ctx.Repo.Repository.DefaultBranch
  327. if !ctx.Repo.GitRepo.IsBranchExist(refName) {
  328. brs, err := ctx.Repo.GitRepo.GetBranches()
  329. if err != nil {
  330. ctx.Handle(500, "GetBranches", err)
  331. return
  332. }
  333. refName = brs[0]
  334. }
  335. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  336. if err != nil {
  337. ctx.Handle(500, "GetBranchCommit", err)
  338. return
  339. }
  340. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  341. ctx.Repo.IsViewBranch = true
  342. } else {
  343. hasMatched := false
  344. parts := strings.Split(ctx.Params("*"), "/")
  345. for i, part := range parts {
  346. refName = strings.TrimPrefix(refName+"/"+part, "/")
  347. if ctx.Repo.GitRepo.IsBranchExist(refName) ||
  348. ctx.Repo.GitRepo.IsTagExist(refName) {
  349. if i < len(parts)-1 {
  350. ctx.Repo.TreePath = strings.Join(parts[i+1:], "/")
  351. }
  352. hasMatched = true
  353. break
  354. }
  355. }
  356. if !hasMatched && len(parts[0]) == 40 {
  357. refName = parts[0]
  358. ctx.Repo.TreePath = strings.Join(parts[1:], "/")
  359. }
  360. if ctx.Repo.GitRepo.IsBranchExist(refName) {
  361. ctx.Repo.IsViewBranch = true
  362. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
  363. if err != nil {
  364. ctx.Handle(500, "GetBranchCommit", err)
  365. return
  366. }
  367. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  368. } else if ctx.Repo.GitRepo.IsTagExist(refName) {
  369. ctx.Repo.IsViewTag = true
  370. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
  371. if err != nil {
  372. ctx.Handle(500, "GetTagCommit", err)
  373. return
  374. }
  375. ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
  376. } else if len(refName) == 40 {
  377. ctx.Repo.IsViewCommit = true
  378. ctx.Repo.CommitID = refName
  379. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
  380. if err != nil {
  381. ctx.Handle(404, "GetCommit", nil)
  382. return
  383. }
  384. } else {
  385. ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
  386. return
  387. }
  388. }
  389. ctx.Repo.BranchName = refName
  390. ctx.Data["BranchName"] = ctx.Repo.BranchName
  391. ctx.Data["CommitID"] = ctx.Repo.CommitID
  392. ctx.Data["TreePath"] = ctx.Repo.TreePath
  393. ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
  394. ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
  395. ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
  396. ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
  397. if err != nil {
  398. ctx.Handle(500, "CommitsCount", err)
  399. return
  400. }
  401. ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
  402. }
  403. }
  404. func RequireRepoAdmin() macaron.Handler {
  405. return func(ctx *Context) {
  406. if !ctx.IsSigned || (!ctx.Repo.IsAdmin() && !ctx.User.IsAdmin) {
  407. ctx.Handle(404, ctx.Req.RequestURI, nil)
  408. return
  409. }
  410. }
  411. }
  412. func RequireRepoWriter() macaron.Handler {
  413. return func(ctx *Context) {
  414. if !ctx.IsSigned || (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) {
  415. ctx.Handle(404, ctx.Req.RequestURI, nil)
  416. return
  417. }
  418. }
  419. }
  420. // GitHookService checks if repository Git hooks service has been enabled.
  421. func GitHookService() macaron.Handler {
  422. return func(ctx *Context) {
  423. if !ctx.User.CanEditGitHook() {
  424. ctx.Handle(404, "GitHookService", nil)
  425. return
  426. }
  427. }
  428. }