gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "errors"
  7. "fmt"
  8. "net/http"
  9. "slices"
  10. "strings"
  11. "code.gitea.io/gitea/models/db"
  12. git_model "code.gitea.io/gitea/models/git"
  13. "code.gitea.io/gitea/models/organization"
  14. access_model "code.gitea.io/gitea/models/perm/access"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. "code.gitea.io/gitea/models/unit"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/cache"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/log"
  21. "code.gitea.io/gitea/modules/optional"
  22. repo_module "code.gitea.io/gitea/modules/repository"
  23. "code.gitea.io/gitea/modules/setting"
  24. api "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/templates"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/web"
  28. "code.gitea.io/gitea/services/context"
  29. "code.gitea.io/gitea/services/convert"
  30. "code.gitea.io/gitea/services/forms"
  31. repo_service "code.gitea.io/gitea/services/repository"
  32. archiver_service "code.gitea.io/gitea/services/repository/archiver"
  33. commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
  34. )
  35. const (
  36. tplCreate templates.TplName = "repo/create"
  37. tplAlertDetails templates.TplName = "base/alert_details"
  38. )
  39. // MustBeNotEmpty render when a repo is a empty git dir
  40. func MustBeNotEmpty(ctx *context.Context) {
  41. if ctx.Repo.Repository.IsEmpty {
  42. ctx.NotFound(nil)
  43. }
  44. }
  45. // MustBeEditable check that repo can be edited
  46. func MustBeEditable(ctx *context.Context) {
  47. if !ctx.Repo.Repository.CanEnableEditor() {
  48. ctx.NotFound(nil)
  49. return
  50. }
  51. }
  52. // MustBeAbleToUpload check that repo can be uploaded to
  53. func MustBeAbleToUpload(ctx *context.Context) {
  54. if !setting.Repository.Upload.Enabled {
  55. ctx.NotFound(nil)
  56. }
  57. }
  58. func CommitInfoCache(ctx *context.Context) {
  59. var err error
  60. ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  61. if err != nil {
  62. ctx.ServerError("GetBranchCommit", err)
  63. return
  64. }
  65. ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
  66. if err != nil {
  67. ctx.ServerError("GetCommitsCount", err)
  68. return
  69. }
  70. ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
  71. ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
  72. }
  73. func checkContextUser(ctx *context.Context, uid int64) *user_model.User {
  74. orgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID)
  75. if err != nil {
  76. ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
  77. return nil
  78. }
  79. var orgsAvailable []*organization.Organization
  80. for i := range orgs {
  81. if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) {
  82. orgsAvailable = append(orgsAvailable, orgs[i])
  83. }
  84. }
  85. ctx.Data["Orgs"] = orgsAvailable
  86. // Not equal means current user is an organization.
  87. if uid == ctx.Doer.ID || uid == 0 {
  88. return ctx.Doer
  89. }
  90. org, err := user_model.GetUserByID(ctx, uid)
  91. if user_model.IsErrUserNotExist(err) {
  92. return ctx.Doer
  93. }
  94. if err != nil {
  95. ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %w", uid, err))
  96. return nil
  97. }
  98. // Check ownership of organization.
  99. if !org.IsOrganization() {
  100. ctx.HTTPError(http.StatusForbidden)
  101. return nil
  102. }
  103. if !ctx.Doer.IsAdmin {
  104. canCreate, err := organization.OrgFromUser(org).CanCreateOrgRepo(ctx, ctx.Doer.ID)
  105. if err != nil {
  106. ctx.ServerError("CanCreateOrgRepo", err)
  107. return nil
  108. } else if !canCreate {
  109. ctx.HTTPError(http.StatusForbidden)
  110. return nil
  111. }
  112. } else {
  113. ctx.Data["Orgs"] = orgs
  114. }
  115. return org
  116. }
  117. func getRepoPrivate(ctx *context.Context) bool {
  118. switch strings.ToLower(setting.Repository.DefaultPrivate) {
  119. case setting.RepoCreatingLastUserVisibility:
  120. return ctx.Doer.LastRepoVisibility
  121. case setting.RepoCreatingPrivate:
  122. return true
  123. case setting.RepoCreatingPublic:
  124. return false
  125. default:
  126. return ctx.Doer.LastRepoVisibility
  127. }
  128. }
  129. func createCommon(ctx *context.Context) {
  130. ctx.Data["Title"] = ctx.Tr("new_repo")
  131. ctx.Data["Gitignores"] = repo_module.Gitignores
  132. ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles
  133. ctx.Data["Licenses"] = repo_module.Licenses
  134. ctx.Data["Readmes"] = repo_module.Readmes
  135. ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
  136. ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepoIn(ctx.Doer)
  137. ctx.Data["MaxCreationLimitOfDoer"] = ctx.Doer.MaxCreationLimit()
  138. ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
  139. ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
  140. }
  141. // Create render creating repository page
  142. func Create(ctx *context.Context) {
  143. createCommon(ctx)
  144. ctxUser := checkContextUser(ctx, ctx.FormInt64("org"))
  145. if ctx.Written() {
  146. return
  147. }
  148. ctx.Data["ContextUser"] = ctxUser
  149. ctx.Data["readme"] = "Default"
  150. ctx.Data["private"] = getRepoPrivate(ctx)
  151. ctx.Data["default_branch"] = setting.Repository.DefaultBranch
  152. ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select")
  153. templateID := ctx.FormInt64("template_id")
  154. if templateID > 0 {
  155. templateRepo, err := repo_model.GetRepositoryByID(ctx, templateID)
  156. if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) {
  157. ctx.Data["repo_template"] = templateID
  158. ctx.Data["repo_template_name"] = templateRepo.Name
  159. }
  160. }
  161. ctx.HTML(http.StatusOK, tplCreate)
  162. }
  163. func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form any) {
  164. switch {
  165. case repo_model.IsErrReachLimitOfRepo(err):
  166. maxCreationLimit := owner.MaxCreationLimit()
  167. msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
  168. ctx.RenderWithErr(msg, tpl, form)
  169. case repo_model.IsErrRepoAlreadyExist(err):
  170. ctx.Data["Err_RepoName"] = true
  171. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
  172. case repo_model.IsErrRepoFilesAlreadyExist(err):
  173. ctx.Data["Err_RepoName"] = true
  174. switch {
  175. case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
  176. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form)
  177. case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
  178. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form)
  179. case setting.Repository.AllowDeleteOfUnadoptedRepositories:
  180. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form)
  181. default:
  182. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form)
  183. }
  184. case db.IsErrNameReserved(err):
  185. ctx.Data["Err_RepoName"] = true
  186. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form)
  187. case db.IsErrNamePatternNotAllowed(err):
  188. ctx.Data["Err_RepoName"] = true
  189. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form)
  190. default:
  191. ctx.ServerError(name, err)
  192. }
  193. }
  194. // CreatePost response for creating repository
  195. func CreatePost(ctx *context.Context) {
  196. createCommon(ctx)
  197. form := web.GetForm(ctx).(*forms.CreateRepoForm)
  198. ctxUser := checkContextUser(ctx, form.UID)
  199. if ctx.Written() {
  200. return
  201. }
  202. ctx.Data["ContextUser"] = ctxUser
  203. if form.RepoTemplate > 0 {
  204. templateRepo, err := repo_model.GetRepositoryByID(ctx, form.RepoTemplate)
  205. if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) {
  206. ctx.Data["repo_template"] = form.RepoTemplate
  207. ctx.Data["repo_template_name"] = templateRepo.Name
  208. }
  209. }
  210. if ctx.HasError() {
  211. ctx.HTML(http.StatusOK, tplCreate)
  212. return
  213. }
  214. var repo *repo_model.Repository
  215. var err error
  216. if form.RepoTemplate > 0 {
  217. opts := repo_service.GenerateRepoOptions{
  218. Name: form.RepoName,
  219. Description: form.Description,
  220. Private: form.Private || setting.Repository.ForcePrivate,
  221. GitContent: form.GitContent,
  222. Topics: form.Topics,
  223. GitHooks: form.GitHooks,
  224. Webhooks: form.Webhooks,
  225. Avatar: form.Avatar,
  226. IssueLabels: form.Labels,
  227. ProtectedBranch: form.ProtectedBranch,
  228. }
  229. if !opts.IsValid() {
  230. ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form)
  231. return
  232. }
  233. templateRepo := getRepository(ctx, form.RepoTemplate)
  234. if ctx.Written() {
  235. return
  236. }
  237. if !templateRepo.IsTemplate {
  238. ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form)
  239. return
  240. }
  241. repo, err = repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, templateRepo, opts)
  242. if err == nil {
  243. log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
  244. ctx.Redirect(repo.Link())
  245. return
  246. }
  247. } else {
  248. repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{
  249. Name: form.RepoName,
  250. Description: form.Description,
  251. Gitignores: form.Gitignores,
  252. IssueLabels: form.IssueLabels,
  253. License: form.License,
  254. Readme: form.Readme,
  255. IsPrivate: form.Private || setting.Repository.ForcePrivate,
  256. DefaultBranch: form.DefaultBranch,
  257. AutoInit: form.AutoInit,
  258. IsTemplate: form.Template,
  259. TrustModel: repo_model.DefaultTrustModel,
  260. ObjectFormatName: form.ObjectFormatName,
  261. })
  262. if err == nil {
  263. log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
  264. ctx.Redirect(repo.Link())
  265. return
  266. }
  267. }
  268. handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form)
  269. }
  270. func handleActionError(ctx *context.Context, err error) {
  271. switch {
  272. case errors.Is(err, user_model.ErrBlockedUser):
  273. ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
  274. case repo_service.IsRepositoryLimitReached(err):
  275. limit := err.(repo_service.LimitReachedError).Limit
  276. ctx.Flash.Error(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit))
  277. case errors.Is(err, util.ErrPermissionDenied):
  278. ctx.HTTPError(http.StatusNotFound)
  279. default:
  280. ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
  281. }
  282. }
  283. // RedirectDownload return a file based on the following infos:
  284. func RedirectDownload(ctx *context.Context) {
  285. var (
  286. vTag = ctx.PathParam("vTag")
  287. fileName = ctx.PathParam("fileName")
  288. )
  289. tagNames := []string{vTag}
  290. curRepo := ctx.Repo.Repository
  291. releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
  292. IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
  293. RepoID: curRepo.ID,
  294. TagNames: tagNames,
  295. })
  296. if err != nil {
  297. ctx.ServerError("RedirectDownload", err)
  298. return
  299. }
  300. if len(releases) == 1 {
  301. release := releases[0]
  302. att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName)
  303. if err != nil {
  304. ctx.HTTPError(http.StatusNotFound)
  305. return
  306. }
  307. if att != nil {
  308. ServeAttachment(ctx, att.UUID)
  309. return
  310. }
  311. } else if len(releases) == 0 && vTag == "latest" {
  312. // GitHub supports the alias "latest" for the latest release
  313. // We only fetch the latest release if the tag is "latest" and no release with the tag "latest" exists
  314. release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
  315. if err != nil {
  316. ctx.HTTPError(http.StatusNotFound)
  317. return
  318. }
  319. att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName)
  320. if err != nil {
  321. ctx.HTTPError(http.StatusNotFound)
  322. return
  323. }
  324. if att != nil {
  325. ServeAttachment(ctx, att.UUID)
  326. return
  327. }
  328. }
  329. ctx.HTTPError(http.StatusNotFound)
  330. }
  331. // Download an archive of a repository
  332. func Download(ctx *context.Context) {
  333. aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"))
  334. if err != nil {
  335. if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
  336. ctx.HTTPError(http.StatusBadRequest, err.Error())
  337. } else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
  338. ctx.HTTPError(http.StatusNotFound, err.Error())
  339. } else {
  340. ctx.ServerError("archiver_service.NewRequest", err)
  341. }
  342. return
  343. }
  344. archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq)
  345. }
  346. // InitiateDownload will enqueue an archival request, as needed. It may submit
  347. // a request that's already in-progress, but the archiver service will just
  348. // kind of drop it on the floor if this is the case.
  349. func InitiateDownload(ctx *context.Context) {
  350. if setting.Repository.StreamArchives {
  351. ctx.JSON(http.StatusOK, map[string]any{
  352. "complete": true,
  353. })
  354. return
  355. }
  356. aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"))
  357. if err != nil {
  358. ctx.HTTPError(http.StatusBadRequest, "invalid archive request")
  359. return
  360. }
  361. if aReq == nil {
  362. ctx.HTTPError(http.StatusNotFound)
  363. return
  364. }
  365. archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID)
  366. if err != nil {
  367. ctx.ServerError("archiver_service.StartArchive", err)
  368. return
  369. }
  370. if archiver == nil || archiver.Status != repo_model.ArchiverReady {
  371. if err := archiver_service.StartArchive(aReq); err != nil {
  372. ctx.ServerError("archiver_service.StartArchive", err)
  373. return
  374. }
  375. }
  376. var completed bool
  377. if archiver != nil && archiver.Status == repo_model.ArchiverReady {
  378. completed = true
  379. }
  380. ctx.JSON(http.StatusOK, map[string]any{
  381. "complete": completed,
  382. })
  383. }
  384. // SearchRepo repositories via options
  385. func SearchRepo(ctx *context.Context) {
  386. page := ctx.FormInt("page")
  387. if page <= 0 {
  388. page = 1
  389. }
  390. opts := repo_model.SearchRepoOptions{
  391. ListOptions: db.ListOptions{
  392. Page: page,
  393. PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
  394. },
  395. Actor: ctx.Doer,
  396. Keyword: ctx.FormTrim("q"),
  397. OwnerID: ctx.FormInt64("uid"),
  398. PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
  399. TeamID: ctx.FormInt64("team_id"),
  400. TopicOnly: ctx.FormBool("topic"),
  401. Collaborate: optional.None[bool](),
  402. Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
  403. Template: optional.None[bool](),
  404. StarredByID: ctx.FormInt64("starredBy"),
  405. IncludeDescription: ctx.FormBool("includeDesc"),
  406. }
  407. if ctx.FormString("template") != "" {
  408. opts.Template = optional.Some(ctx.FormBool("template"))
  409. }
  410. if ctx.FormBool("exclusive") {
  411. opts.Collaborate = optional.Some(false)
  412. }
  413. mode := ctx.FormString("mode")
  414. switch mode {
  415. case "source":
  416. opts.Fork = optional.Some(false)
  417. opts.Mirror = optional.Some(false)
  418. case "fork":
  419. opts.Fork = optional.Some(true)
  420. case "mirror":
  421. opts.Mirror = optional.Some(true)
  422. case "collaborative":
  423. opts.Mirror = optional.Some(false)
  424. opts.Collaborate = optional.Some(true)
  425. case "":
  426. default:
  427. ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode))
  428. return
  429. }
  430. if ctx.FormString("archived") != "" {
  431. opts.Archived = optional.Some(ctx.FormBool("archived"))
  432. }
  433. if ctx.FormString("is_private") != "" {
  434. opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
  435. }
  436. sortMode := ctx.FormString("sort")
  437. if len(sortMode) > 0 {
  438. sortOrder := ctx.FormString("order")
  439. if len(sortOrder) == 0 {
  440. sortOrder = "asc"
  441. }
  442. if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
  443. if orderBy, ok := searchModeMap[sortMode]; ok {
  444. opts.OrderBy = orderBy
  445. } else {
  446. ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode))
  447. return
  448. }
  449. } else {
  450. ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder))
  451. return
  452. }
  453. }
  454. // To improve performance when only the count is requested
  455. if ctx.FormBool("count_only") {
  456. if count, err := repo_model.CountRepository(ctx, opts); err != nil {
  457. log.Error("CountRepository: %v", err)
  458. ctx.JSON(http.StatusInternalServerError, nil) // frontend JS doesn't handle error response (same as below)
  459. } else {
  460. ctx.SetTotalCountHeader(count)
  461. ctx.JSONOK()
  462. }
  463. return
  464. }
  465. repos, count, err := repo_model.SearchRepository(ctx, opts)
  466. if err != nil {
  467. log.Error("SearchRepository: %v", err)
  468. ctx.JSON(http.StatusInternalServerError, nil)
  469. return
  470. }
  471. ctx.SetTotalCountHeader(count)
  472. latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos)
  473. if err != nil {
  474. log.Error("FindReposLastestCommitStatuses: %v", err)
  475. ctx.JSON(http.StatusInternalServerError, nil)
  476. return
  477. }
  478. if !ctx.Repo.CanRead(unit.TypeActions) {
  479. git_model.CommitStatusesHideActionsURL(ctx, latestCommitStatuses)
  480. }
  481. results := make([]*repo_service.WebSearchRepository, len(repos))
  482. for i, repo := range repos {
  483. results[i] = &repo_service.WebSearchRepository{
  484. Repository: &api.Repository{
  485. ID: repo.ID,
  486. FullName: repo.FullName(),
  487. Fork: repo.IsFork,
  488. Private: repo.IsPrivate,
  489. Template: repo.IsTemplate,
  490. Mirror: repo.IsMirror,
  491. Stars: repo.NumStars,
  492. HTMLURL: repo.HTMLURL(ctx),
  493. Link: repo.Link(),
  494. Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
  495. },
  496. }
  497. if latestCommitStatuses[i] != nil {
  498. results[i].LatestCommitStatus = latestCommitStatuses[i]
  499. results[i].LocaleLatestCommitStatus = latestCommitStatuses[i].LocaleString(ctx.Locale)
  500. }
  501. }
  502. ctx.JSON(http.StatusOK, repo_service.WebSearchResults{
  503. OK: true,
  504. Data: results,
  505. })
  506. }
  507. type branchTagSearchResponse struct {
  508. Results []string `json:"results"`
  509. }
  510. // GetBranchesList get branches for current repo'
  511. func GetBranchesList(ctx *context.Context) {
  512. branchOpts := git_model.FindBranchOptions{
  513. RepoID: ctx.Repo.Repository.ID,
  514. IsDeletedBranch: optional.Some(false),
  515. ListOptions: db.ListOptionsAll,
  516. }
  517. branches, err := git_model.FindBranchNames(ctx, branchOpts)
  518. if err != nil {
  519. ctx.JSON(http.StatusInternalServerError, err)
  520. return
  521. }
  522. resp := &branchTagSearchResponse{}
  523. // always put default branch on the top if it exists
  524. if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) {
  525. branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
  526. branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
  527. }
  528. resp.Results = branches
  529. ctx.JSON(http.StatusOK, resp)
  530. }
  531. // GetTagList get tag list for current repo
  532. func GetTagList(ctx *context.Context) {
  533. tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
  534. if err != nil {
  535. ctx.JSON(http.StatusInternalServerError, err)
  536. return
  537. }
  538. resp := &branchTagSearchResponse{}
  539. resp.Results = tags
  540. ctx.JSON(http.StatusOK, resp)
  541. }
  542. func PrepareBranchList(ctx *context.Context) {
  543. branchOpts := git_model.FindBranchOptions{
  544. RepoID: ctx.Repo.Repository.ID,
  545. IsDeletedBranch: optional.Some(false),
  546. ListOptions: db.ListOptionsAll,
  547. }
  548. brs, err := git_model.FindBranchNames(ctx, branchOpts)
  549. if err != nil {
  550. ctx.ServerError("GetBranches", err)
  551. return
  552. }
  553. // always put default branch on the top if it exists
  554. if slices.Contains(brs, ctx.Repo.Repository.DefaultBranch) {
  555. brs = util.SliceRemoveAll(brs, ctx.Repo.Repository.DefaultBranch)
  556. brs = append([]string{ctx.Repo.Repository.DefaultBranch}, brs...)
  557. }
  558. ctx.Data["Branches"] = brs
  559. }