1// Copyright 2017 The Gitea 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 5package models 6 7import ( 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/models/perm" 13 repo_model "code.gitea.io/gitea/models/repo" 14 "code.gitea.io/gitea/models/unit" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/structs" 17 "code.gitea.io/gitea/modules/util" 18 19 "xorm.io/builder" 20) 21 22// RepositoryListDefaultPageSize is the default number of repositories 23// to load in memory when running administrative tasks on all (or almost 24// all) of them. 25// The number should be low enough to avoid filling up all RAM with 26// repository data... 27const RepositoryListDefaultPageSize = 64 28 29// RepositoryList contains a list of repositories 30type RepositoryList []*repo_model.Repository 31 32func (repos RepositoryList) Len() int { 33 return len(repos) 34} 35 36func (repos RepositoryList) Less(i, j int) bool { 37 return repos[i].FullName() < repos[j].FullName() 38} 39 40func (repos RepositoryList) Swap(i, j int) { 41 repos[i], repos[j] = repos[j], repos[i] 42} 43 44// RepositoryListOfMap make list from values of map 45func RepositoryListOfMap(repoMap map[int64]*repo_model.Repository) RepositoryList { 46 return RepositoryList(valuesRepository(repoMap)) 47} 48 49func (repos RepositoryList) loadAttributes(e db.Engine) error { 50 if len(repos) == 0 { 51 return nil 52 } 53 54 set := make(map[int64]struct{}) 55 repoIDs := make([]int64, len(repos)) 56 for i := range repos { 57 set[repos[i].OwnerID] = struct{}{} 58 repoIDs[i] = repos[i].ID 59 } 60 61 // Load owners. 62 users := make(map[int64]*user_model.User, len(set)) 63 if err := e. 64 Where("id > 0"). 65 In("id", keysInt64(set)). 66 Find(&users); err != nil { 67 return fmt.Errorf("find users: %v", err) 68 } 69 for i := range repos { 70 repos[i].Owner = users[repos[i].OwnerID] 71 } 72 73 // Load primary language. 74 stats := make(repo_model.LanguageStatList, 0, len(repos)) 75 if err := e. 76 Where("`is_primary` = ? AND `language` != ?", true, "other"). 77 In("`repo_id`", repoIDs). 78 Find(&stats); err != nil { 79 return fmt.Errorf("find primary languages: %v", err) 80 } 81 stats.LoadAttributes() 82 for i := range repos { 83 for _, st := range stats { 84 if st.RepoID == repos[i].ID { 85 repos[i].PrimaryLanguage = st 86 break 87 } 88 } 89 } 90 91 return nil 92} 93 94// LoadAttributes loads the attributes for the given RepositoryList 95func (repos RepositoryList) LoadAttributes() error { 96 return repos.loadAttributes(db.GetEngine(db.DefaultContext)) 97} 98 99// SearchRepoOptions holds the search options 100type SearchRepoOptions struct { 101 db.ListOptions 102 Actor *user_model.User 103 Keyword string 104 OwnerID int64 105 PriorityOwnerID int64 106 TeamID int64 107 OrderBy db.SearchOrderBy 108 Private bool // Include private repositories in results 109 StarredByID int64 110 WatchedByID int64 111 AllPublic bool // Include also all public repositories of users and public organisations 112 AllLimited bool // Include also all public repositories of limited organisations 113 // None -> include public and private 114 // True -> include just private 115 // False -> include just public 116 IsPrivate util.OptionalBool 117 // None -> include collaborative AND non-collaborative 118 // True -> include just collaborative 119 // False -> include just non-collaborative 120 Collaborate util.OptionalBool 121 // None -> include forks AND non-forks 122 // True -> include just forks 123 // False -> include just non-forks 124 Fork util.OptionalBool 125 // None -> include templates AND non-templates 126 // True -> include just templates 127 // False -> include just non-templates 128 Template util.OptionalBool 129 // None -> include mirrors AND non-mirrors 130 // True -> include just mirrors 131 // False -> include just non-mirrors 132 Mirror util.OptionalBool 133 // None -> include archived AND non-archived 134 // True -> include just archived 135 // False -> include just non-archived 136 Archived util.OptionalBool 137 // only search topic name 138 TopicOnly bool 139 // include description in keyword search 140 IncludeDescription bool 141 // None -> include has milestones AND has no milestone 142 // True -> include just has milestones 143 // False -> include just has no milestone 144 HasMilestones util.OptionalBool 145 // LowerNames represents valid lower names to restrict to 146 LowerNames []string 147} 148 149// SearchOrderBy is used to sort the result 150type SearchOrderBy string 151 152func (s SearchOrderBy) String() string { 153 return string(s) 154} 155 156// Strings for sorting result 157const ( 158 SearchOrderByAlphabetically SearchOrderBy = "name ASC" 159 SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" 160 SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" 161 SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" 162 SearchOrderByOldest SearchOrderBy = "created_unix ASC" 163 SearchOrderByNewest SearchOrderBy = "created_unix DESC" 164 SearchOrderBySize SearchOrderBy = "size ASC" 165 SearchOrderBySizeReverse SearchOrderBy = "size DESC" 166 SearchOrderByID SearchOrderBy = "id ASC" 167 SearchOrderByIDReverse SearchOrderBy = "id DESC" 168 SearchOrderByStars SearchOrderBy = "num_stars ASC" 169 SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" 170 SearchOrderByForks SearchOrderBy = "num_forks ASC" 171 SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" 172) 173 174// userOwnedRepoCond returns user ownered repositories 175func userOwnedRepoCond(userID int64) builder.Cond { 176 return builder.Eq{ 177 "repository.owner_id": userID, 178 } 179} 180 181// userAssignedRepoCond return user as assignee repositories list 182func userAssignedRepoCond(id string, userID int64) builder.Cond { 183 return builder.And( 184 builder.Eq{ 185 "repository.is_private": false, 186 }, 187 builder.In(id, 188 builder.Select("issue.repo_id").From("issue_assignees"). 189 InnerJoin("issue", "issue.id = issue_assignees.issue_id"). 190 Where(builder.Eq{ 191 "issue_assignees.assignee_id": userID, 192 }), 193 ), 194 ) 195} 196 197// userCreateIssueRepoCond return user created issues repositories list 198func userCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond { 199 return builder.And( 200 builder.Eq{ 201 "repository.is_private": false, 202 }, 203 builder.In(id, 204 builder.Select("issue.repo_id").From("issue"). 205 Where(builder.Eq{ 206 "issue.poster_id": userID, 207 "issue.is_pull": isPull, 208 }), 209 ), 210 ) 211} 212 213// userMentionedRepoCond return user metinoed repositories list 214func userMentionedRepoCond(id string, userID int64) builder.Cond { 215 return builder.And( 216 builder.Eq{ 217 "repository.is_private": false, 218 }, 219 builder.In(id, 220 builder.Select("issue.repo_id").From("issue_user"). 221 InnerJoin("issue", "issue.id = issue_user.issue_id"). 222 Where(builder.Eq{ 223 "issue_user.is_mentioned": true, 224 "issue_user.uid": userID, 225 }), 226 ), 227 ) 228} 229 230// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access 231func teamUnitsRepoCond(id string, userID, orgID, teamID int64, units ...unit.Type) builder.Cond { 232 return builder.In(id, 233 builder.Select("repo_id").From("team_repo").Where( 234 builder.Eq{ 235 "team_id": teamID, 236 }.And( 237 builder.In( 238 "team_id", builder.Select("team_id").From("team_user").Where( 239 builder.Eq{ 240 "uid": userID, 241 }, 242 ), 243 )).And( 244 builder.In( 245 "team_id", builder.Select("team_id").From("team_unit").Where( 246 builder.Eq{ 247 "`team_unit`.org_id": orgID, 248 }.And( 249 builder.In("`team_unit`.type", units), 250 ), 251 ), 252 ), 253 ), 254 )) 255} 256 257// userCollaborationRepoCond returns user as collabrators repositories list 258func userCollaborationRepoCond(idStr string, userID int64) builder.Cond { 259 return builder.In(idStr, builder.Select("repo_id"). 260 From("`access`"). 261 Where(builder.And( 262 builder.Eq{"`access`.user_id": userID}, 263 builder.Gt{"`access`.mode": int(perm.AccessModeNone)}, 264 )), 265 ) 266} 267 268// userOrgTeamRepoCond selects repos that the given user has access to through team membership 269func userOrgTeamRepoCond(idStr string, userID int64) builder.Cond { 270 return builder.In(idStr, userOrgTeamRepoBuilder(userID)) 271} 272 273// userOrgTeamRepoBuilder returns repo ids where user's teams can access. 274func userOrgTeamRepoBuilder(userID int64) *builder.Builder { 275 return builder.Select("`team_repo`.repo_id"). 276 From("team_repo"). 277 Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"). 278 Where(builder.Eq{"`team_user`.uid": userID}) 279} 280 281// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit. 282func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder { 283 return userOrgTeamRepoBuilder(userID). 284 Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id"). 285 Where(builder.Eq{"`team_unit`.`type`": unitType}) 286} 287 288// userOrgUnitRepoCond selects repos that the given user has access to through org and the special unit 289func userOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond { 290 return builder.In(idStr, 291 userOrgTeamUnitRepoBuilder(userID, unitType). 292 And(builder.Eq{"`team_unit`.org_id": orgID}), 293 ) 294} 295 296// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations 297func userOrgPublicRepoCond(userID int64) builder.Cond { 298 return builder.And( 299 builder.Eq{"`repository`.is_private": false}, 300 builder.In("`repository`.owner_id", 301 builder.Select("`org_user`.org_id"). 302 From("org_user"). 303 Where(builder.Eq{"`org_user`.uid": userID}), 304 ), 305 ) 306} 307 308// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations 309func userOrgPublicRepoCondPrivate(userID int64) builder.Cond { 310 return builder.And( 311 builder.Eq{"`repository`.is_private": false}, 312 builder.In("`repository`.owner_id", 313 builder.Select("`org_user`.org_id"). 314 From("org_user"). 315 Join("INNER", "`user`", "`user`.id = `org_user`.org_id"). 316 Where(builder.Eq{ 317 "`org_user`.uid": userID, 318 "`user`.`type`": user_model.UserTypeOrganization, 319 "`user`.visibility": structs.VisibleTypePrivate, 320 }), 321 ), 322 ) 323} 324 325// userOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization 326func userOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond { 327 return userOrgPublicRepoCond(userID). 328 And(builder.Eq{"`repository`.owner_id": orgID}) 329} 330 331// SearchRepositoryCondition creates a query condition according search repository options 332func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { 333 cond := builder.NewCond() 334 335 if opts.Private { 336 if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { 337 // OK we're in the context of a User 338 cond = cond.And(accessibleRepositoryCondition(opts.Actor)) 339 } 340 } else { 341 // Not looking at private organisations and users 342 // We should be able to see all non-private repositories that 343 // isn't in a private or limited organisation. 344 cond = cond.And( 345 builder.Eq{"is_private": false}, 346 builder.NotIn("owner_id", builder.Select("id").From("`user`").Where( 347 builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}), 348 ))) 349 } 350 351 if opts.IsPrivate != util.OptionalBoolNone { 352 cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) 353 } 354 355 if opts.Template != util.OptionalBoolNone { 356 cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) 357 } 358 359 // Restrict to starred repositories 360 if opts.StarredByID > 0 { 361 cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) 362 } 363 364 // Restrict to watched repositories 365 if opts.WatchedByID > 0 { 366 cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) 367 } 368 369 // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate 370 if opts.OwnerID > 0 { 371 accessCond := builder.NewCond() 372 if opts.Collaborate != util.OptionalBoolTrue { 373 accessCond = builder.Eq{"owner_id": opts.OwnerID} 374 } 375 376 if opts.Collaborate != util.OptionalBoolFalse { 377 // A Collaboration is: 378 collaborateCond := builder.And( 379 // 1. Repository we don't own 380 builder.Neq{"owner_id": opts.OwnerID}, 381 // 2. But we can see because of: 382 builder.Or( 383 // A. We have access 384 userCollaborationRepoCond("`repository`.id", opts.OwnerID), 385 // B. We are in a team for 386 userOrgTeamRepoCond("`repository`.id", opts.OwnerID), 387 // C. Public repositories in organizations that we are member of 388 userOrgPublicRepoCondPrivate(opts.OwnerID), 389 ), 390 ) 391 if !opts.Private { 392 collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false)) 393 } 394 395 accessCond = accessCond.Or(collaborateCond) 396 } 397 398 if opts.AllPublic { 399 accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})))) 400 } 401 402 if opts.AllLimited { 403 accessCond = accessCond.Or(builder.Eq{"is_private": false}.And(builder.In("owner_id", builder.Select("`user`.id").From("`user`").Where(builder.Eq{"`user`.visibility": structs.VisibleTypeLimited})))) 404 } 405 406 cond = cond.And(accessCond) 407 } 408 409 if opts.TeamID > 0 { 410 cond = cond.And(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").From("team_repo").Where(builder.Eq{"`team_repo`.team_id": opts.TeamID}))) 411 } 412 413 if opts.Keyword != "" { 414 // separate keyword 415 subQueryCond := builder.NewCond() 416 for _, v := range strings.Split(opts.Keyword, ",") { 417 if opts.TopicOnly { 418 subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) 419 } else { 420 subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)}) 421 } 422 } 423 subQuery := builder.Select("repo_topic.repo_id").From("repo_topic"). 424 Join("INNER", "topic", "topic.id = repo_topic.topic_id"). 425 Where(subQueryCond). 426 GroupBy("repo_topic.repo_id") 427 428 keywordCond := builder.In("id", subQuery) 429 if !opts.TopicOnly { 430 likes := builder.NewCond() 431 for _, v := range strings.Split(opts.Keyword, ",") { 432 likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) 433 if opts.IncludeDescription { 434 likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) 435 } 436 } 437 keywordCond = keywordCond.Or(likes) 438 } 439 cond = cond.And(keywordCond) 440 } 441 442 if opts.Fork != util.OptionalBoolNone { 443 cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) 444 } 445 446 if opts.Mirror != util.OptionalBoolNone { 447 cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) 448 } 449 450 if opts.Actor != nil && opts.Actor.IsRestricted { 451 cond = cond.And(accessibleRepositoryCondition(opts.Actor)) 452 } 453 454 if opts.Archived != util.OptionalBoolNone { 455 cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) 456 } 457 458 switch opts.HasMilestones { 459 case util.OptionalBoolTrue: 460 cond = cond.And(builder.Gt{"num_milestones": 0}) 461 case util.OptionalBoolFalse: 462 cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) 463 } 464 465 return cond 466} 467 468// SearchRepository returns repositories based on search options, 469// it returns results in given range and number of total results. 470func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { 471 cond := SearchRepositoryCondition(opts) 472 return SearchRepositoryByCondition(opts, cond, true) 473} 474 475// SearchRepositoryByCondition search repositories by condition 476func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) { 477 sess, count, err := searchRepositoryByCondition(opts, cond) 478 if err != nil { 479 return nil, 0, err 480 } 481 482 defaultSize := 50 483 if opts.PageSize > 0 { 484 defaultSize = opts.PageSize 485 } 486 repos := make(RepositoryList, 0, defaultSize) 487 if err := sess.Find(&repos); err != nil { 488 return nil, 0, fmt.Errorf("Repo: %v", err) 489 } 490 491 if opts.PageSize <= 0 { 492 count = int64(len(repos)) 493 } 494 495 if loadAttributes { 496 if err := repos.loadAttributes(sess); err != nil { 497 return nil, 0, fmt.Errorf("LoadAttributes: %v", err) 498 } 499 } 500 501 return repos, count, nil 502} 503 504func searchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) { 505 if opts.Page <= 0 { 506 opts.Page = 1 507 } 508 509 if len(opts.OrderBy) == 0 { 510 opts.OrderBy = db.SearchOrderByAlphabetically 511 } 512 513 if opts.PriorityOwnerID > 0 { 514 opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy)) 515 } 516 517 sess := db.GetEngine(db.DefaultContext) 518 519 var count int64 520 if opts.PageSize > 0 { 521 var err error 522 count, err = sess. 523 Where(cond). 524 Count(new(repo_model.Repository)) 525 if err != nil { 526 return nil, 0, fmt.Errorf("Count: %v", err) 527 } 528 } 529 530 sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) 531 if opts.PageSize > 0 { 532 sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) 533 } 534 return sess, count, nil 535} 536 537// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible 538func accessibleRepositoryCondition(user *user_model.User) builder.Cond { 539 cond := builder.NewCond() 540 541 if user == nil || !user.IsRestricted || user.ID <= 0 { 542 orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate} 543 if user == nil || user.ID <= 0 { 544 orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited) 545 } 546 // 1. Be able to see all non-private repositories that either: 547 cond = cond.Or(builder.And( 548 builder.Eq{"`repository`.is_private": false}, 549 // 2. Aren't in an private organisation or limited organisation if we're not logged in 550 builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where( 551 builder.And( 552 builder.Eq{"type": user_model.UserTypeOrganization}, 553 builder.In("visibility", orgVisibilityLimit)), 554 )))) 555 } 556 557 if user != nil { 558 cond = cond.Or( 559 // 2. Be able to see all repositories that we have access to 560 userCollaborationRepoCond("`repository`.id", user.ID), 561 // 3. Repositories that we directly own 562 builder.Eq{"`repository`.owner_id": user.ID}, 563 // 4. Be able to see all repositories that we are in a team 564 userOrgTeamRepoCond("`repository`.id", user.ID), 565 // 5. Be able to see all public repos in private organizations that we are an org_user of 566 userOrgPublicRepoCond(user.ID), 567 ) 568 } 569 570 return cond 571} 572 573// SearchRepositoryByName takes keyword and part of repository name to search, 574// it returns results in given range and number of total results. 575func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { 576 opts.IncludeDescription = false 577 return SearchRepository(opts) 578} 579 580// SearchRepositoryIDs takes keyword and part of repository name to search, 581// it returns results in given range and number of total results. 582func SearchRepositoryIDs(opts *SearchRepoOptions) ([]int64, int64, error) { 583 opts.IncludeDescription = false 584 585 cond := SearchRepositoryCondition(opts) 586 587 sess, count, err := searchRepositoryByCondition(opts, cond) 588 if err != nil { 589 return nil, 0, err 590 } 591 592 defaultSize := 50 593 if opts.PageSize > 0 { 594 defaultSize = opts.PageSize 595 } 596 597 ids := make([]int64, 0, defaultSize) 598 err = sess.Select("id").Table("repository").Find(&ids) 599 if opts.PageSize <= 0 { 600 count = int64(len(ids)) 601 } 602 603 return ids, count, err 604} 605 606// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered. 607func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder { 608 // NB: Please note this code needs to still work if user is nil 609 return builder.Select("id").From("repository").Where(accessibleRepositoryCondition(user)) 610} 611 612// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id 613func FindUserAccessibleRepoIDs(user *user_model.User) ([]int64, error) { 614 repoIDs := make([]int64, 0, 10) 615 if err := db.GetEngine(db.DefaultContext). 616 Table("repository"). 617 Cols("id"). 618 Where(accessibleRepositoryCondition(user)). 619 Find(&repoIDs); err != nil { 620 return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err) 621 } 622 return repoIDs, nil 623} 624 625// GetUserRepositories returns a list of repositories of given user. 626func GetUserRepositories(opts *SearchRepoOptions) ([]*repo_model.Repository, int64, error) { 627 if len(opts.OrderBy) == 0 { 628 opts.OrderBy = "updated_unix DESC" 629 } 630 631 cond := builder.NewCond() 632 cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID}) 633 if !opts.Private { 634 cond = cond.And(builder.Eq{"is_private": false}) 635 } 636 637 if opts.LowerNames != nil && len(opts.LowerNames) > 0 { 638 cond = cond.And(builder.In("lower_name", opts.LowerNames)) 639 } 640 641 sess := db.GetEngine(db.DefaultContext) 642 643 count, err := sess.Where(cond).Count(new(repo_model.Repository)) 644 if err != nil { 645 return nil, 0, fmt.Errorf("Count: %v", err) 646 } 647 648 sess = sess.Where(cond).OrderBy(opts.OrderBy.String()) 649 repos := make([]*repo_model.Repository, 0, opts.PageSize) 650 return repos, count, db.SetSessionPagination(sess, opts).Find(&repos) 651} 652