您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "errors"
  8. "fmt"
  9. "net/http"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/modules/base"
  14. "code.gitea.io/gitea/modules/context"
  15. "code.gitea.io/gitea/modules/log"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/web"
  18. archiver_service "code.gitea.io/gitea/services/archiver"
  19. "code.gitea.io/gitea/services/forms"
  20. repo_service "code.gitea.io/gitea/services/repository"
  21. )
  22. const (
  23. tplCreate base.TplName = "repo/create"
  24. tplAlertDetails base.TplName = "base/alert_details"
  25. )
  26. // MustBeNotEmpty render when a repo is a empty git dir
  27. func MustBeNotEmpty(ctx *context.Context) {
  28. if ctx.Repo.Repository.IsEmpty {
  29. ctx.NotFound("MustBeNotEmpty", nil)
  30. }
  31. }
  32. // MustBeEditable check that repo can be edited
  33. func MustBeEditable(ctx *context.Context) {
  34. if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
  35. ctx.NotFound("", nil)
  36. return
  37. }
  38. }
  39. // MustBeAbleToUpload check that repo can be uploaded to
  40. func MustBeAbleToUpload(ctx *context.Context) {
  41. if !setting.Repository.Upload.Enabled {
  42. ctx.NotFound("", nil)
  43. }
  44. }
  45. func checkContextUser(ctx *context.Context, uid int64) *models.User {
  46. orgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID)
  47. if err != nil {
  48. ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
  49. return nil
  50. }
  51. if !ctx.User.IsAdmin {
  52. orgsAvailable := []*models.User{}
  53. for i := 0; i < len(orgs); i++ {
  54. if orgs[i].CanCreateRepo() {
  55. orgsAvailable = append(orgsAvailable, orgs[i])
  56. }
  57. }
  58. ctx.Data["Orgs"] = orgsAvailable
  59. } else {
  60. ctx.Data["Orgs"] = orgs
  61. }
  62. // Not equal means current user is an organization.
  63. if uid == ctx.User.ID || uid == 0 {
  64. return ctx.User
  65. }
  66. org, err := models.GetUserByID(uid)
  67. if models.IsErrUserNotExist(err) {
  68. return ctx.User
  69. }
  70. if err != nil {
  71. ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %v", uid, err))
  72. return nil
  73. }
  74. // Check ownership of organization.
  75. if !org.IsOrganization() {
  76. ctx.Error(http.StatusForbidden)
  77. return nil
  78. }
  79. if !ctx.User.IsAdmin {
  80. canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
  81. if err != nil {
  82. ctx.ServerError("CanCreateOrgRepo", err)
  83. return nil
  84. } else if !canCreate {
  85. ctx.Error(http.StatusForbidden)
  86. return nil
  87. }
  88. } else {
  89. ctx.Data["Orgs"] = orgs
  90. }
  91. return org
  92. }
  93. func getRepoPrivate(ctx *context.Context) bool {
  94. switch strings.ToLower(setting.Repository.DefaultPrivate) {
  95. case setting.RepoCreatingLastUserVisibility:
  96. return ctx.User.LastRepoVisibility
  97. case setting.RepoCreatingPrivate:
  98. return true
  99. case setting.RepoCreatingPublic:
  100. return false
  101. default:
  102. return ctx.User.LastRepoVisibility
  103. }
  104. }
  105. // Create render creating repository page
  106. func Create(ctx *context.Context) {
  107. ctx.Data["Title"] = ctx.Tr("new_repo")
  108. // Give default value for template to render.
  109. ctx.Data["Gitignores"] = models.Gitignores
  110. ctx.Data["LabelTemplates"] = models.LabelTemplates
  111. ctx.Data["Licenses"] = models.Licenses
  112. ctx.Data["Readmes"] = models.Readmes
  113. ctx.Data["readme"] = "Default"
  114. ctx.Data["private"] = getRepoPrivate(ctx)
  115. ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
  116. ctx.Data["default_branch"] = setting.Repository.DefaultBranch
  117. ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
  118. if ctx.Written() {
  119. return
  120. }
  121. ctx.Data["ContextUser"] = ctxUser
  122. ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select")
  123. templateID := ctx.QueryInt64("template_id")
  124. if templateID > 0 {
  125. templateRepo, err := models.GetRepositoryByID(templateID)
  126. if err == nil && templateRepo.CheckUnitUser(ctxUser, models.UnitTypeCode) {
  127. ctx.Data["repo_template"] = templateID
  128. ctx.Data["repo_template_name"] = templateRepo.Name
  129. }
  130. }
  131. ctx.Data["CanCreateRepo"] = ctx.User.CanCreateRepo()
  132. ctx.Data["MaxCreationLimit"] = ctx.User.MaxCreationLimit()
  133. ctx.HTML(http.StatusOK, tplCreate)
  134. }
  135. func handleCreateError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
  136. switch {
  137. case models.IsErrReachLimitOfRepo(err):
  138. ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.MaxCreationLimit()), tpl, form)
  139. case models.IsErrRepoAlreadyExist(err):
  140. ctx.Data["Err_RepoName"] = true
  141. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
  142. case models.IsErrRepoFilesAlreadyExist(err):
  143. ctx.Data["Err_RepoName"] = true
  144. switch {
  145. case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
  146. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form)
  147. case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
  148. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form)
  149. case setting.Repository.AllowDeleteOfUnadoptedRepositories:
  150. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form)
  151. default:
  152. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form)
  153. }
  154. case models.IsErrNameReserved(err):
  155. ctx.Data["Err_RepoName"] = true
  156. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
  157. case models.IsErrNamePatternNotAllowed(err):
  158. ctx.Data["Err_RepoName"] = true
  159. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
  160. default:
  161. ctx.ServerError(name, err)
  162. }
  163. }
  164. // CreatePost response for creating repository
  165. func CreatePost(ctx *context.Context) {
  166. form := web.GetForm(ctx).(*forms.CreateRepoForm)
  167. ctx.Data["Title"] = ctx.Tr("new_repo")
  168. ctx.Data["Gitignores"] = models.Gitignores
  169. ctx.Data["LabelTemplates"] = models.LabelTemplates
  170. ctx.Data["Licenses"] = models.Licenses
  171. ctx.Data["Readmes"] = models.Readmes
  172. ctx.Data["CanCreateRepo"] = ctx.User.CanCreateRepo()
  173. ctx.Data["MaxCreationLimit"] = ctx.User.MaxCreationLimit()
  174. ctxUser := checkContextUser(ctx, form.UID)
  175. if ctx.Written() {
  176. return
  177. }
  178. ctx.Data["ContextUser"] = ctxUser
  179. if ctx.HasError() {
  180. ctx.HTML(http.StatusOK, tplCreate)
  181. return
  182. }
  183. var repo *models.Repository
  184. var err error
  185. if form.RepoTemplate > 0 {
  186. opts := models.GenerateRepoOptions{
  187. Name: form.RepoName,
  188. Description: form.Description,
  189. Private: form.Private,
  190. GitContent: form.GitContent,
  191. Topics: form.Topics,
  192. GitHooks: form.GitHooks,
  193. Webhooks: form.Webhooks,
  194. Avatar: form.Avatar,
  195. IssueLabels: form.Labels,
  196. }
  197. if !opts.IsValid() {
  198. ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form)
  199. return
  200. }
  201. templateRepo := getRepository(ctx, form.RepoTemplate)
  202. if ctx.Written() {
  203. return
  204. }
  205. if !templateRepo.IsTemplate {
  206. ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form)
  207. return
  208. }
  209. repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts)
  210. if err == nil {
  211. log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
  212. ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name)
  213. return
  214. }
  215. } else {
  216. repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
  217. Name: form.RepoName,
  218. Description: form.Description,
  219. Gitignores: form.Gitignores,
  220. IssueLabels: form.IssueLabels,
  221. License: form.License,
  222. Readme: form.Readme,
  223. IsPrivate: form.Private || setting.Repository.ForcePrivate,
  224. DefaultBranch: form.DefaultBranch,
  225. AutoInit: form.AutoInit,
  226. IsTemplate: form.Template,
  227. TrustModel: models.ToTrustModel(form.TrustModel),
  228. })
  229. if err == nil {
  230. log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
  231. ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name)
  232. return
  233. }
  234. }
  235. handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form)
  236. }
  237. // Action response for actions to a repository
  238. func Action(ctx *context.Context) {
  239. var err error
  240. switch ctx.Params(":action") {
  241. case "watch":
  242. err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, true)
  243. case "unwatch":
  244. err = models.WatchRepo(ctx.User.ID, ctx.Repo.Repository.ID, false)
  245. case "star":
  246. err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, true)
  247. case "unstar":
  248. err = models.StarRepo(ctx.User.ID, ctx.Repo.Repository.ID, false)
  249. case "accept_transfer":
  250. err = acceptOrRejectRepoTransfer(ctx, true)
  251. case "reject_transfer":
  252. err = acceptOrRejectRepoTransfer(ctx, false)
  253. case "desc": // FIXME: this is not used
  254. if !ctx.Repo.IsOwner() {
  255. ctx.Error(http.StatusNotFound)
  256. return
  257. }
  258. ctx.Repo.Repository.Description = ctx.Query("desc")
  259. ctx.Repo.Repository.Website = ctx.Query("site")
  260. err = models.UpdateRepository(ctx.Repo.Repository, false)
  261. }
  262. if err != nil {
  263. ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
  264. return
  265. }
  266. ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink)
  267. }
  268. func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
  269. repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
  270. if err != nil {
  271. return err
  272. }
  273. if err := repoTransfer.LoadAttributes(); err != nil {
  274. return err
  275. }
  276. if !repoTransfer.CanUserAcceptTransfer(ctx.User) {
  277. return errors.New("user does not have enough permissions")
  278. }
  279. if accept {
  280. if err := repo_service.TransferOwnership(repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
  281. return err
  282. }
  283. ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
  284. } else {
  285. if err := models.CancelRepositoryTransfer(ctx.Repo.Repository); err != nil {
  286. return err
  287. }
  288. ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
  289. }
  290. ctx.Redirect(ctx.Repo.Repository.HTMLURL())
  291. return nil
  292. }
  293. // RedirectDownload return a file based on the following infos:
  294. func RedirectDownload(ctx *context.Context) {
  295. var (
  296. vTag = ctx.Params("vTag")
  297. fileName = ctx.Params("fileName")
  298. )
  299. tagNames := []string{vTag}
  300. curRepo := ctx.Repo.Repository
  301. releases, err := models.GetReleasesByRepoIDAndNames(models.DefaultDBContext(), curRepo.ID, tagNames)
  302. if err != nil {
  303. if models.IsErrAttachmentNotExist(err) {
  304. ctx.Error(http.StatusNotFound)
  305. return
  306. }
  307. ctx.ServerError("RedirectDownload", err)
  308. return
  309. }
  310. if len(releases) == 1 {
  311. release := releases[0]
  312. att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName)
  313. if err != nil {
  314. ctx.Error(http.StatusNotFound)
  315. return
  316. }
  317. if att != nil {
  318. ctx.Redirect(att.DownloadURL())
  319. return
  320. }
  321. }
  322. ctx.Error(http.StatusNotFound)
  323. }
  324. // InitiateDownload will enqueue an archival request, as needed. It may submit
  325. // a request that's already in-progress, but the archiver service will just
  326. // kind of drop it on the floor if this is the case.
  327. func InitiateDownload(ctx *context.Context) {
  328. uri := ctx.Params("*")
  329. aReq := archiver_service.DeriveRequestFrom(ctx, uri)
  330. if aReq == nil {
  331. ctx.Error(http.StatusNotFound)
  332. return
  333. }
  334. complete := aReq.IsComplete()
  335. if !complete {
  336. aReq = archiver_service.ArchiveRepository(aReq)
  337. complete, _ = aReq.TimedWaitForCompletion(ctx, 2*time.Second)
  338. }
  339. ctx.JSON(http.StatusOK, map[string]interface{}{
  340. "complete": complete,
  341. })
  342. }