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