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