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