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 13 kB

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