code.gitea.io/gitea@v1.21.7/routers/web/repo/repo.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2020 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package repo 6 7 import ( 8 "errors" 9 "fmt" 10 "net/http" 11 "slices" 12 "strings" 13 14 "code.gitea.io/gitea/models" 15 "code.gitea.io/gitea/models/db" 16 git_model "code.gitea.io/gitea/models/git" 17 "code.gitea.io/gitea/models/organization" 18 access_model "code.gitea.io/gitea/models/perm/access" 19 repo_model "code.gitea.io/gitea/models/repo" 20 "code.gitea.io/gitea/models/unit" 21 user_model "code.gitea.io/gitea/models/user" 22 "code.gitea.io/gitea/modules/base" 23 "code.gitea.io/gitea/modules/cache" 24 "code.gitea.io/gitea/modules/context" 25 "code.gitea.io/gitea/modules/git" 26 "code.gitea.io/gitea/modules/log" 27 repo_module "code.gitea.io/gitea/modules/repository" 28 "code.gitea.io/gitea/modules/setting" 29 "code.gitea.io/gitea/modules/storage" 30 api "code.gitea.io/gitea/modules/structs" 31 "code.gitea.io/gitea/modules/util" 32 "code.gitea.io/gitea/modules/web" 33 "code.gitea.io/gitea/services/convert" 34 "code.gitea.io/gitea/services/forms" 35 repo_service "code.gitea.io/gitea/services/repository" 36 archiver_service "code.gitea.io/gitea/services/repository/archiver" 37 ) 38 39 const ( 40 tplCreate base.TplName = "repo/create" 41 tplAlertDetails base.TplName = "base/alert_details" 42 ) 43 44 // MustBeNotEmpty render when a repo is a empty git dir 45 func MustBeNotEmpty(ctx *context.Context) { 46 if ctx.Repo.Repository.IsEmpty { 47 ctx.NotFound("MustBeNotEmpty", nil) 48 } 49 } 50 51 // MustBeEditable check that repo can be edited 52 func MustBeEditable(ctx *context.Context) { 53 if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit { 54 ctx.NotFound("", nil) 55 return 56 } 57 } 58 59 // MustBeAbleToUpload check that repo can be uploaded to 60 func MustBeAbleToUpload(ctx *context.Context) { 61 if !setting.Repository.Upload.Enabled { 62 ctx.NotFound("", nil) 63 } 64 } 65 66 func CommitInfoCache(ctx *context.Context) { 67 var err error 68 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) 69 if err != nil { 70 ctx.ServerError("GetBranchCommit", err) 71 return 72 } 73 ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() 74 if err != nil { 75 ctx.ServerError("GetCommitsCount", err) 76 return 77 } 78 ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount 79 ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache()) 80 } 81 82 func checkContextUser(ctx *context.Context, uid int64) *user_model.User { 83 orgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, ctx.Doer.ID) 84 if err != nil { 85 ctx.ServerError("GetOrgsCanCreateRepoByUserID", err) 86 return nil 87 } 88 89 if !ctx.Doer.IsAdmin { 90 orgsAvailable := []*organization.Organization{} 91 for i := 0; i < len(orgs); i++ { 92 if orgs[i].CanCreateRepo() { 93 orgsAvailable = append(orgsAvailable, orgs[i]) 94 } 95 } 96 ctx.Data["Orgs"] = orgsAvailable 97 } else { 98 ctx.Data["Orgs"] = orgs 99 } 100 101 // Not equal means current user is an organization. 102 if uid == ctx.Doer.ID || uid == 0 { 103 return ctx.Doer 104 } 105 106 org, err := user_model.GetUserByID(ctx, uid) 107 if user_model.IsErrUserNotExist(err) { 108 return ctx.Doer 109 } 110 111 if err != nil { 112 ctx.ServerError("GetUserByID", fmt.Errorf("[%d]: %w", uid, err)) 113 return nil 114 } 115 116 // Check ownership of organization. 117 if !org.IsOrganization() { 118 ctx.Error(http.StatusForbidden) 119 return nil 120 } 121 if !ctx.Doer.IsAdmin { 122 canCreate, err := organization.OrgFromUser(org).CanCreateOrgRepo(ctx.Doer.ID) 123 if err != nil { 124 ctx.ServerError("CanCreateOrgRepo", err) 125 return nil 126 } else if !canCreate { 127 ctx.Error(http.StatusForbidden) 128 return nil 129 } 130 } else { 131 ctx.Data["Orgs"] = orgs 132 } 133 return org 134 } 135 136 func getRepoPrivate(ctx *context.Context) bool { 137 switch strings.ToLower(setting.Repository.DefaultPrivate) { 138 case setting.RepoCreatingLastUserVisibility: 139 return ctx.Doer.LastRepoVisibility 140 case setting.RepoCreatingPrivate: 141 return true 142 case setting.RepoCreatingPublic: 143 return false 144 default: 145 return ctx.Doer.LastRepoVisibility 146 } 147 } 148 149 // Create render creating repository page 150 func Create(ctx *context.Context) { 151 ctx.Data["Title"] = ctx.Tr("new_repo") 152 153 // Give default value for template to render. 154 ctx.Data["Gitignores"] = repo_module.Gitignores 155 ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles 156 ctx.Data["Licenses"] = repo_module.Licenses 157 ctx.Data["Readmes"] = repo_module.Readmes 158 ctx.Data["readme"] = "Default" 159 ctx.Data["private"] = getRepoPrivate(ctx) 160 ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate 161 ctx.Data["default_branch"] = setting.Repository.DefaultBranch 162 163 ctxUser := checkContextUser(ctx, ctx.FormInt64("org")) 164 if ctx.Written() { 165 return 166 } 167 ctx.Data["ContextUser"] = ctxUser 168 169 ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select") 170 templateID := ctx.FormInt64("template_id") 171 if templateID > 0 { 172 templateRepo, err := repo_model.GetRepositoryByID(ctx, templateID) 173 if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) { 174 ctx.Data["repo_template"] = templateID 175 ctx.Data["repo_template_name"] = templateRepo.Name 176 } 177 } 178 179 ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() 180 ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() 181 182 ctx.HTML(http.StatusOK, tplCreate) 183 } 184 185 func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form any) { 186 switch { 187 case repo_model.IsErrReachLimitOfRepo(err): 188 maxCreationLimit := owner.MaxCreationLimit() 189 msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) 190 ctx.RenderWithErr(msg, tpl, form) 191 case repo_model.IsErrRepoAlreadyExist(err): 192 ctx.Data["Err_RepoName"] = true 193 ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) 194 case repo_model.IsErrRepoFilesAlreadyExist(err): 195 ctx.Data["Err_RepoName"] = true 196 switch { 197 case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): 198 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) 199 case setting.Repository.AllowAdoptionOfUnadoptedRepositories: 200 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) 201 case setting.Repository.AllowDeleteOfUnadoptedRepositories: 202 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) 203 default: 204 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form) 205 } 206 case db.IsErrNameReserved(err): 207 ctx.Data["Err_RepoName"] = true 208 ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) 209 case db.IsErrNamePatternNotAllowed(err): 210 ctx.Data["Err_RepoName"] = true 211 ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) 212 default: 213 ctx.ServerError(name, err) 214 } 215 } 216 217 // CreatePost response for creating repository 218 func CreatePost(ctx *context.Context) { 219 form := web.GetForm(ctx).(*forms.CreateRepoForm) 220 ctx.Data["Title"] = ctx.Tr("new_repo") 221 222 ctx.Data["Gitignores"] = repo_module.Gitignores 223 ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles 224 ctx.Data["Licenses"] = repo_module.Licenses 225 ctx.Data["Readmes"] = repo_module.Readmes 226 227 ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() 228 ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() 229 230 ctxUser := checkContextUser(ctx, form.UID) 231 if ctx.Written() { 232 return 233 } 234 ctx.Data["ContextUser"] = ctxUser 235 236 if ctx.HasError() { 237 ctx.HTML(http.StatusOK, tplCreate) 238 return 239 } 240 241 var repo *repo_model.Repository 242 var err error 243 if form.RepoTemplate > 0 { 244 opts := repo_module.GenerateRepoOptions{ 245 Name: form.RepoName, 246 Description: form.Description, 247 Private: form.Private, 248 GitContent: form.GitContent, 249 Topics: form.Topics, 250 GitHooks: form.GitHooks, 251 Webhooks: form.Webhooks, 252 Avatar: form.Avatar, 253 IssueLabels: form.Labels, 254 ProtectedBranch: form.ProtectedBranch, 255 } 256 257 if !opts.IsValid() { 258 ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form) 259 return 260 } 261 262 templateRepo := getRepository(ctx, form.RepoTemplate) 263 if ctx.Written() { 264 return 265 } 266 267 if !templateRepo.IsTemplate { 268 ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form) 269 return 270 } 271 272 repo, err = repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, templateRepo, opts) 273 if err == nil { 274 log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) 275 ctx.Redirect(repo.Link()) 276 return 277 } 278 } else { 279 repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_service.CreateRepoOptions{ 280 Name: form.RepoName, 281 Description: form.Description, 282 Gitignores: form.Gitignores, 283 IssueLabels: form.IssueLabels, 284 License: form.License, 285 Readme: form.Readme, 286 IsPrivate: form.Private || setting.Repository.ForcePrivate, 287 DefaultBranch: form.DefaultBranch, 288 AutoInit: form.AutoInit, 289 IsTemplate: form.Template, 290 TrustModel: repo_model.ToTrustModel(form.TrustModel), 291 }) 292 if err == nil { 293 log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) 294 ctx.Redirect(repo.Link()) 295 return 296 } 297 } 298 299 handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form) 300 } 301 302 // Action response for actions to a repository 303 func Action(ctx *context.Context) { 304 var err error 305 switch ctx.Params(":action") { 306 case "watch": 307 err = repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true) 308 case "unwatch": 309 err = repo_model.WatchRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false) 310 case "star": 311 err = repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, true) 312 case "unstar": 313 err = repo_model.StarRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, false) 314 case "accept_transfer": 315 err = acceptOrRejectRepoTransfer(ctx, true) 316 case "reject_transfer": 317 err = acceptOrRejectRepoTransfer(ctx, false) 318 case "desc": // FIXME: this is not used 319 if !ctx.Repo.IsOwner() { 320 ctx.Error(http.StatusNotFound) 321 return 322 } 323 324 ctx.Repo.Repository.Description = ctx.FormString("desc") 325 ctx.Repo.Repository.Website = ctx.FormString("site") 326 err = repo_service.UpdateRepository(ctx, ctx.Repo.Repository, false) 327 } 328 329 if err != nil { 330 ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) 331 return 332 } 333 334 ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.Repo.RepoLink) 335 } 336 337 func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { 338 repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) 339 if err != nil { 340 return err 341 } 342 343 if err := repoTransfer.LoadAttributes(ctx); err != nil { 344 return err 345 } 346 347 if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) { 348 return errors.New("user does not have enough permissions") 349 } 350 351 if accept { 352 if ctx.Repo.GitRepo != nil { 353 ctx.Repo.GitRepo.Close() 354 ctx.Repo.GitRepo = nil 355 } 356 357 if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil { 358 return err 359 } 360 ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success")) 361 } else { 362 if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { 363 return err 364 } 365 ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) 366 } 367 368 ctx.Redirect(ctx.Repo.Repository.Link()) 369 return nil 370 } 371 372 // RedirectDownload return a file based on the following infos: 373 func RedirectDownload(ctx *context.Context) { 374 var ( 375 vTag = ctx.Params("vTag") 376 fileName = ctx.Params("fileName") 377 ) 378 tagNames := []string{vTag} 379 curRepo := ctx.Repo.Repository 380 releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames) 381 if err != nil { 382 ctx.ServerError("RedirectDownload", err) 383 return 384 } 385 if len(releases) == 1 { 386 release := releases[0] 387 att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName) 388 if err != nil { 389 ctx.Error(http.StatusNotFound) 390 return 391 } 392 if att != nil { 393 ServeAttachment(ctx, att.UUID) 394 return 395 } 396 } else if len(releases) == 0 && vTag == "latest" { 397 // GitHub supports the alias "latest" for the latest release 398 // We only fetch the latest release if the tag is "latest" and no release with the tag "latest" exists 399 release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) 400 if err != nil { 401 ctx.Error(http.StatusNotFound) 402 return 403 } 404 att, err := repo_model.GetAttachmentByReleaseIDFileName(ctx, release.ID, fileName) 405 if err != nil { 406 ctx.Error(http.StatusNotFound) 407 return 408 } 409 if att != nil { 410 ServeAttachment(ctx, att.UUID) 411 return 412 } 413 } 414 ctx.Error(http.StatusNotFound) 415 } 416 417 // Download an archive of a repository 418 func Download(ctx *context.Context) { 419 uri := ctx.Params("*") 420 aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) 421 if err != nil { 422 if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { 423 ctx.Error(http.StatusBadRequest, err.Error()) 424 } else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) { 425 ctx.Error(http.StatusNotFound, err.Error()) 426 } else { 427 ctx.ServerError("archiver_service.NewRequest", err) 428 } 429 return 430 } 431 432 archiver, err := aReq.Await(ctx) 433 if err != nil { 434 ctx.ServerError("archiver.Await", err) 435 return 436 } 437 438 download(ctx, aReq.GetArchiveName(), archiver) 439 } 440 441 func download(ctx *context.Context, archiveName string, archiver *repo_model.RepoArchiver) { 442 downloadName := ctx.Repo.Repository.Name + "-" + archiveName 443 444 rPath := archiver.RelativePath() 445 if setting.RepoArchive.Storage.MinioConfig.ServeDirect { 446 // If we have a signed url (S3, object storage), redirect to this directly. 447 u, err := storage.RepoArchives.URL(rPath, downloadName) 448 if u != nil && err == nil { 449 ctx.Redirect(u.String()) 450 return 451 } 452 } 453 454 // If we have matched and access to release or issue 455 fr, err := storage.RepoArchives.Open(rPath) 456 if err != nil { 457 ctx.ServerError("Open", err) 458 return 459 } 460 defer fr.Close() 461 462 ctx.ServeContent(fr, &context.ServeHeaderOptions{ 463 Filename: downloadName, 464 LastModified: archiver.CreatedUnix.AsLocalTime(), 465 }) 466 } 467 468 // InitiateDownload will enqueue an archival request, as needed. It may submit 469 // a request that's already in-progress, but the archiver service will just 470 // kind of drop it on the floor if this is the case. 471 func InitiateDownload(ctx *context.Context) { 472 uri := ctx.Params("*") 473 aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) 474 if err != nil { 475 ctx.ServerError("archiver_service.NewRequest", err) 476 return 477 } 478 if aReq == nil { 479 ctx.Error(http.StatusNotFound) 480 return 481 } 482 483 archiver, err := repo_model.GetRepoArchiver(ctx, aReq.RepoID, aReq.Type, aReq.CommitID) 484 if err != nil { 485 ctx.ServerError("archiver_service.StartArchive", err) 486 return 487 } 488 if archiver == nil || archiver.Status != repo_model.ArchiverReady { 489 if err := archiver_service.StartArchive(aReq); err != nil { 490 ctx.ServerError("archiver_service.StartArchive", err) 491 return 492 } 493 } 494 495 var completed bool 496 if archiver != nil && archiver.Status == repo_model.ArchiverReady { 497 completed = true 498 } 499 500 ctx.JSON(http.StatusOK, map[string]any{ 501 "complete": completed, 502 }) 503 } 504 505 // SearchRepo repositories via options 506 func SearchRepo(ctx *context.Context) { 507 opts := &repo_model.SearchRepoOptions{ 508 ListOptions: db.ListOptions{ 509 Page: ctx.FormInt("page"), 510 PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), 511 }, 512 Actor: ctx.Doer, 513 Keyword: ctx.FormTrim("q"), 514 OwnerID: ctx.FormInt64("uid"), 515 PriorityOwnerID: ctx.FormInt64("priority_owner_id"), 516 TeamID: ctx.FormInt64("team_id"), 517 TopicOnly: ctx.FormBool("topic"), 518 Collaborate: util.OptionalBoolNone, 519 Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")), 520 Template: util.OptionalBoolNone, 521 StarredByID: ctx.FormInt64("starredBy"), 522 IncludeDescription: ctx.FormBool("includeDesc"), 523 } 524 525 if ctx.FormString("template") != "" { 526 opts.Template = util.OptionalBoolOf(ctx.FormBool("template")) 527 } 528 529 if ctx.FormBool("exclusive") { 530 opts.Collaborate = util.OptionalBoolFalse 531 } 532 533 mode := ctx.FormString("mode") 534 switch mode { 535 case "source": 536 opts.Fork = util.OptionalBoolFalse 537 opts.Mirror = util.OptionalBoolFalse 538 case "fork": 539 opts.Fork = util.OptionalBoolTrue 540 case "mirror": 541 opts.Mirror = util.OptionalBoolTrue 542 case "collaborative": 543 opts.Mirror = util.OptionalBoolFalse 544 opts.Collaborate = util.OptionalBoolTrue 545 case "": 546 default: 547 ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode)) 548 return 549 } 550 551 if ctx.FormString("archived") != "" { 552 opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived")) 553 } 554 555 if ctx.FormString("is_private") != "" { 556 opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private")) 557 } 558 559 sortMode := ctx.FormString("sort") 560 if len(sortMode) > 0 { 561 sortOrder := ctx.FormString("order") 562 if len(sortOrder) == 0 { 563 sortOrder = "asc" 564 } 565 if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok { 566 if orderBy, ok := searchModeMap[sortMode]; ok { 567 opts.OrderBy = orderBy 568 } else { 569 ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode)) 570 return 571 } 572 } else { 573 ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder)) 574 return 575 } 576 } 577 578 var err error 579 repos, count, err := repo_model.SearchRepository(ctx, opts) 580 if err != nil { 581 ctx.JSON(http.StatusInternalServerError, api.SearchError{ 582 OK: false, 583 Error: err.Error(), 584 }) 585 return 586 } 587 588 ctx.SetTotalCountHeader(count) 589 590 // To improve performance when only the count is requested 591 if ctx.FormBool("count_only") { 592 return 593 } 594 595 // collect the latest commit of each repo 596 // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment 597 repoBranchNames := make(map[int64]string, len(repos)) 598 for _, repo := range repos { 599 repoBranchNames[repo.ID] = repo.DefaultBranch 600 } 601 602 repoIDsToLatestCommitSHAs, err := git_model.FindBranchesByRepoAndBranchName(ctx, repoBranchNames) 603 if err != nil { 604 log.Error("FindBranchesByRepoAndBranchName: %v", err) 605 return 606 } 607 608 // call the database O(1) times to get the commit statuses for all repos 609 repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{}) 610 if err != nil { 611 log.Error("GetLatestCommitStatusForPairs: %v", err) 612 return 613 } 614 615 results := make([]*repo_service.WebSearchRepository, len(repos)) 616 for i, repo := range repos { 617 latestCommitStatus := git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]) 618 619 results[i] = &repo_service.WebSearchRepository{ 620 Repository: &api.Repository{ 621 ID: repo.ID, 622 FullName: repo.FullName(), 623 Fork: repo.IsFork, 624 Private: repo.IsPrivate, 625 Template: repo.IsTemplate, 626 Mirror: repo.IsMirror, 627 Stars: repo.NumStars, 628 HTMLURL: repo.HTMLURL(), 629 Link: repo.Link(), 630 Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate, 631 }, 632 LatestCommitStatus: latestCommitStatus, 633 LocaleLatestCommitStatus: latestCommitStatus.LocaleString(ctx.Locale), 634 } 635 } 636 637 ctx.JSON(http.StatusOK, repo_service.WebSearchResults{ 638 OK: true, 639 Data: results, 640 }) 641 } 642 643 type branchTagSearchResponse struct { 644 Results []string `json:"results"` 645 } 646 647 // GetBranchesList get branches for current repo' 648 func GetBranchesList(ctx *context.Context) { 649 branchOpts := git_model.FindBranchOptions{ 650 RepoID: ctx.Repo.Repository.ID, 651 IsDeletedBranch: util.OptionalBoolFalse, 652 ListOptions: db.ListOptions{ 653 ListAll: true, 654 }, 655 } 656 branches, err := git_model.FindBranchNames(ctx, branchOpts) 657 if err != nil { 658 ctx.JSON(http.StatusInternalServerError, err) 659 return 660 } 661 resp := &branchTagSearchResponse{} 662 // always put default branch on the top if it exists 663 if slices.Contains(branches, ctx.Repo.Repository.DefaultBranch) { 664 branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch) 665 branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...) 666 } 667 resp.Results = branches 668 ctx.JSON(http.StatusOK, resp) 669 } 670 671 // GetTagList get tag list for current repo 672 func GetTagList(ctx *context.Context) { 673 tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) 674 if err != nil { 675 ctx.JSON(http.StatusInternalServerError, err) 676 return 677 } 678 resp := &branchTagSearchResponse{} 679 resp.Results = tags 680 ctx.JSON(http.StatusOK, resp) 681 } 682 683 func PrepareBranchList(ctx *context.Context) { 684 branchOpts := git_model.FindBranchOptions{ 685 RepoID: ctx.Repo.Repository.ID, 686 IsDeletedBranch: util.OptionalBoolFalse, 687 ListOptions: db.ListOptions{ 688 ListAll: true, 689 }, 690 } 691 brs, err := git_model.FindBranchNames(ctx, branchOpts) 692 if err != nil { 693 ctx.ServerError("GetBranches", err) 694 return 695 } 696 // always put default branch on the top if it exists 697 if slices.Contains(brs, ctx.Repo.Repository.DefaultBranch) { 698 brs = util.SliceRemoveAll(brs, ctx.Repo.Repository.DefaultBranch) 699 brs = append([]string{ctx.Repo.Repository.DefaultBranch}, brs...) 700 } 701 ctx.Data["Branches"] = brs 702 }