gitea/models/repo/repo_list.go
Philip Peterson c5e78fc7ad Do not mutate incoming options to SearchRepositoryByName (#34553)
Similar to #34544, this PR changes the `opts` argument in
`SearchRepositoryByName()` to be passed by value instead of by pointer,
as its mutations do not escape the function scope and are not used
elsewhere. This simplifies reasoning about the function and avoids
unnecessary pointer usage.

This insight emerged during an initial attempt to refactor
`RenderUserSearch()`, which currently intermixes multiple concerns.

---------

Co-authored-by: Philip Peterson <philip-peterson@users.noreply.github.com>
2025-06-02 17:33:25 +00:00

774 lines
25 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
// RepositoryListDefaultPageSize is the default number of repositories
// to load in memory when running administrative tasks on all (or almost
// all) of them.
// The number should be low enough to avoid filling up all RAM with
// repository data...
const RepositoryListDefaultPageSize = 64
// RepositoryList contains a list of repositories
type RepositoryList []*Repository
func (repos RepositoryList) Len() int {
return len(repos)
}
func (repos RepositoryList) Less(i, j int) bool {
return repos[i].FullName() < repos[j].FullName()
}
func (repos RepositoryList) Swap(i, j int) {
repos[i], repos[j] = repos[j], repos[i]
}
// ValuesRepository converts a repository map to a list
// FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18
func ValuesRepository(m map[int64]*Repository) []*Repository {
values := make([]*Repository, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
// RepositoryListOfMap make list from values of map
func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList {
return RepositoryList(ValuesRepository(repoMap))
}
func (repos RepositoryList) LoadUnits(ctx context.Context) error {
if len(repos) == 0 {
return nil
}
// Load units.
units := make([]*RepoUnit, 0, len(repos)*6)
if err := db.GetEngine(ctx).
In("repo_id", repos.IDs()).
Find(&units); err != nil {
return fmt.Errorf("find units: %w", err)
}
unitsMap := make(map[int64][]*RepoUnit, len(repos))
for _, unit := range units {
if !unit.Type.UnitGlobalDisabled() {
unitsMap[unit.RepoID] = append(unitsMap[unit.RepoID], unit)
}
}
for _, repo := range repos {
repo.Units = unitsMap[repo.ID]
}
return nil
}
func (repos RepositoryList) IDs() []int64 {
repoIDs := make([]int64, len(repos))
for i := range repos {
repoIDs[i] = repos[i].ID
}
return repoIDs
}
func (repos RepositoryList) LoadOwners(ctx context.Context) error {
if len(repos) == 0 {
return nil
}
userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
return repo.OwnerID, true
})
// Load owners.
users := make(map[int64]*user_model.User, len(userIDs))
if err := db.GetEngine(ctx).
Where("id > 0").
In("id", userIDs).
Find(&users); err != nil {
return fmt.Errorf("find users: %w", err)
}
for i := range repos {
repos[i].Owner = users[repos[i].OwnerID]
}
return nil
}
func (repos RepositoryList) LoadLanguageStats(ctx context.Context) error {
if len(repos) == 0 {
return nil
}
// Load primary language.
stats := make(LanguageStatList, 0, len(repos))
if err := db.GetEngine(ctx).
Where("`is_primary` = ? AND `language` != ?", true, "other").
In("`repo_id`", repos.IDs()).
Find(&stats); err != nil {
return fmt.Errorf("find primary languages: %w", err)
}
stats.LoadAttributes()
for i := range repos {
for _, st := range stats {
if st.RepoID == repos[i].ID {
repos[i].PrimaryLanguage = st
break
}
}
}
return nil
}
// LoadAttributes loads the attributes for the given RepositoryList
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
if err := repos.LoadOwners(ctx); err != nil {
return err
}
return repos.LoadLanguageStats(ctx)
}
// SearchRepoOptions holds the search options
type SearchRepoOptions struct {
db.ListOptions
Actor *user_model.User
Keyword string
OwnerID int64
PriorityOwnerID int64
TeamID int64
OrderBy db.SearchOrderBy
Private bool // Include private repositories in results
StarredByID int64
WatchedByID int64
AllPublic bool // Include also all public repositories of users and public organisations
AllLimited bool // Include also all public repositories of limited organisations
// None -> include public and private
// True -> include just private
// False -> include just public
IsPrivate optional.Option[bool]
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
// False -> include just non-collaborative
Collaborate optional.Option[bool]
// What type of unit the user can be collaborative in,
// it is ignored if Collaborate is False.
// TypeInvalid means any unit type.
UnitType unit.Type
// None -> include forks AND non-forks
// True -> include just forks
// False -> include just non-forks
Fork optional.Option[bool]
// If Fork option is True, you can use this option to limit the forks of a special repo by repo id.
ForkFrom int64
// None -> include templates AND non-templates
// True -> include just templates
// False -> include just non-templates
Template optional.Option[bool]
// None -> include mirrors AND non-mirrors
// True -> include just mirrors
// False -> include just non-mirrors
Mirror optional.Option[bool]
// None -> include archived AND non-archived
// True -> include just archived
// False -> include just non-archived
Archived optional.Option[bool]
// only search topic name
TopicOnly bool
// only search repositories with specified primary language
Language string
// include description in keyword search
IncludeDescription bool
// None -> include has milestones AND has no milestone
// True -> include just has milestones
// False -> include just has no milestone
HasMilestones optional.Option[bool]
// LowerNames represents valid lower names to restrict to
LowerNames []string
// When specified true, apply some filters over the conditions:
// - Don't show forks, when opts.Fork is OptionalBoolNone.
// - Do not display repositories that don't have a description, an icon and topics.
OnlyShowRelevant bool
}
// UserOwnedRepoCond returns user ownered repositories
func UserOwnedRepoCond(userID int64) builder.Cond {
return builder.Eq{
"repository.owner_id": userID,
}
}
// UserAssignedRepoCond return user as assignee repositories list
func UserAssignedRepoCond(id string, userID int64) builder.Cond {
return builder.And(
builder.Eq{
"repository.is_private": false,
},
builder.In(id,
builder.Select("issue.repo_id").From("issue_assignees").
InnerJoin("issue", "issue.id = issue_assignees.issue_id").
Where(builder.Eq{
"issue_assignees.assignee_id": userID,
}),
),
)
}
// UserCreateIssueRepoCond return user created issues repositories list
func UserCreateIssueRepoCond(id string, userID int64, isPull bool) builder.Cond {
return builder.And(
builder.Eq{
"repository.is_private": false,
},
builder.In(id,
builder.Select("issue.repo_id").From("issue").
Where(builder.Eq{
"issue.poster_id": userID,
"issue.is_pull": isPull,
}),
),
)
}
// UserMentionedRepoCond return user metinoed repositories list
func UserMentionedRepoCond(id string, userID int64) builder.Cond {
return builder.And(
builder.Eq{
"repository.is_private": false,
},
builder.In(id,
builder.Select("issue.repo_id").From("issue_user").
InnerJoin("issue", "issue.id = issue_user.issue_id").
Where(builder.Eq{
"issue_user.is_mentioned": true,
"issue_user.uid": userID,
}),
),
)
}
// UserAccessRepoCond returns a condition for selecting all repositories a user has unit independent access to
func UserAccessRepoCond(idStr string, userID int64) builder.Cond {
return builder.In(idStr, builder.Select("repo_id").
From("`access`").
Where(builder.And(
builder.Eq{"`access`.user_id": userID},
builder.Gt{"`access`.mode": int(perm.AccessModeNone)},
)),
)
}
// userCollaborationRepoCond returns a condition for selecting all repositories a user is collaborator in
func UserCollaborationRepoCond(idStr string, userID int64) builder.Cond {
return builder.In(idStr, builder.Select("repo_id").
From("`collaboration`").
Where(builder.And(
builder.Eq{"`collaboration`.user_id": userID},
)),
)
}
// UserOrgTeamRepoCond selects repos that the given user has access to through team membership
func UserOrgTeamRepoCond(idStr string, userID int64) builder.Cond {
return builder.In(idStr, userOrgTeamRepoBuilder(userID))
}
// userOrgTeamRepoBuilder returns repo ids where user's teams can access.
func userOrgTeamRepoBuilder(userID int64) *builder.Builder {
return builder.Select("`team_repo`.repo_id").
From("team_repo").
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id").
Where(builder.Eq{"`team_user`.uid": userID})
}
// userOrgTeamUnitRepoBuilder returns repo ids where user's teams can access the special unit.
func userOrgTeamUnitRepoBuilder(userID int64, unitType unit.Type) *builder.Builder {
return userOrgTeamRepoBuilder(userID).
Join("INNER", "team_unit", "`team_unit`.team_id = `team_repo`.team_id").
Where(builder.Eq{"`team_unit`.`type`": unitType}).
And(builder.Gt{"`team_unit`.`access_mode`": int(perm.AccessModeNone)})
}
// userOrgTeamUnitRepoCond returns a condition to select repo ids where user's teams can access the special unit.
func userOrgTeamUnitRepoCond(idStr string, userID int64, unitType unit.Type) builder.Cond {
return builder.In(idStr, userOrgTeamUnitRepoBuilder(userID, unitType))
}
// UserOrgUnitRepoCond selects repos that the given user has access to through org and the special unit
func UserOrgUnitRepoCond(idStr string, userID, orgID int64, unitType unit.Type) builder.Cond {
return builder.In(idStr,
userOrgTeamUnitRepoBuilder(userID, unitType).
And(builder.Eq{"`team_unit`.org_id": orgID}),
)
}
// userOrgPublicRepoCond returns the condition that one user could access all public repositories in organizations
func userOrgPublicRepoCond(userID int64) builder.Cond {
return builder.And(
builder.Eq{"`repository`.is_private": false},
builder.In("`repository`.owner_id",
builder.Select("`org_user`.org_id").
From("org_user").
Where(builder.Eq{"`org_user`.uid": userID}),
),
)
}
// userOrgPublicRepoCondPrivate returns the condition that one user could access all public repositories in private organizations
func userOrgPublicRepoCondPrivate(userID int64) builder.Cond {
return builder.And(
builder.Eq{"`repository`.is_private": false},
builder.In("`repository`.owner_id",
builder.Select("`org_user`.org_id").
From("org_user").
Join("INNER", "`user`", "`user`.id = `org_user`.org_id").
Where(builder.Eq{
"`org_user`.uid": userID,
"`user`.`type`": user_model.UserTypeOrganization,
"`user`.visibility": structs.VisibleTypePrivate,
}),
),
)
}
// UserOrgPublicUnitRepoCond returns the condition that one user could access all public repositories in the special organization
func UserOrgPublicUnitRepoCond(userID, orgID int64) builder.Cond {
return userOrgPublicRepoCond(userID).
And(builder.Eq{"`repository`.owner_id": orgID})
}
// SearchRepositoryCondition creates a query condition according search repository options
func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond {
cond := builder.NewCond()
if opts.Private {
if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID {
// OK we're in the context of a User
cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid))
}
} else {
// Not looking at private organisations and users
// We should be able to see all non-private repositories that
// isn't in a private or limited organisation.
cond = cond.And(
builder.Eq{"is_private": false},
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(
builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}),
)))
}
if opts.IsPrivate.Has() {
cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.Value()})
}
if opts.Template.Has() {
cond = cond.And(builder.Eq{"is_template": opts.Template.Value()})
}
// Restrict to starred repositories
if opts.StarredByID > 0 {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
}
// Restrict to watched repositories
if opts.WatchedByID > 0 {
cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID})))
}
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 {
accessCond := builder.NewCond()
if !opts.Collaborate.Value() {
accessCond = builder.Eq{"owner_id": opts.OwnerID}
}
if opts.Collaborate.ValueOrDefault(true) {
// A Collaboration is:
collaborateCond := builder.NewCond()
// 1. Repository we don't own
collaborateCond = collaborateCond.And(builder.Neq{"owner_id": opts.OwnerID})
// 2. But we can see because of:
{
userAccessCond := builder.NewCond()
// A. We have unit independent access
userAccessCond = userAccessCond.Or(UserAccessRepoCond("`repository`.id", opts.OwnerID))
// B. We are in a team for
if opts.UnitType == unit.TypeInvalid {
userAccessCond = userAccessCond.Or(UserOrgTeamRepoCond("`repository`.id", opts.OwnerID))
} else {
userAccessCond = userAccessCond.Or(userOrgTeamUnitRepoCond("`repository`.id", opts.OwnerID, opts.UnitType))
}
// C. Public repositories in organizations that we are member of
userAccessCond = userAccessCond.Or(userOrgPublicRepoCondPrivate(opts.OwnerID))
collaborateCond = collaborateCond.And(userAccessCond)
}
if !opts.Private {
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))
}
accessCond = accessCond.Or(collaborateCond)
}
if opts.AllPublic {
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}))))
}
if opts.AllLimited {
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}))))
}
cond = cond.And(accessCond)
}
if opts.TeamID > 0 {
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})))
}
if opts.Keyword != "" {
// separate keyword
subQueryCond := builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") {
if opts.TopicOnly {
subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)})
} else {
subQueryCond = subQueryCond.Or(builder.Like{"topic.name", strings.ToLower(v)})
}
}
subQuery := builder.Select("repo_topic.repo_id").From("repo_topic").
Join("INNER", "topic", "topic.id = repo_topic.topic_id").
Where(subQueryCond).
GroupBy("repo_topic.repo_id")
keywordCond := builder.In("id", subQuery)
if !opts.TopicOnly {
likes := builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
// If the string looks like "org/repo", match against that pattern too
if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 {
pieces := strings.Split(opts.Keyword, "/")
ownerName := pieces[0]
repoName := pieces[1]
likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)}))
}
if opts.IncludeDescription {
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
}
}
keywordCond = keywordCond.Or(likes)
}
cond = cond.And(keywordCond)
}
if opts.Language != "" {
cond = cond.And(builder.In("id", builder.
Select("repo_id").
From("language_stat").
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
}
if opts.Fork.Has() || opts.OnlyShowRelevant {
if opts.OnlyShowRelevant && !opts.Fork.Has() {
cond = cond.And(builder.Eq{"is_fork": false})
} else {
cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
if opts.ForkFrom > 0 && opts.Fork.Value() {
cond = cond.And(builder.Eq{"fork_id": opts.ForkFrom})
}
}
}
if opts.Mirror.Has() {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror.Value()})
}
if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid))
}
if opts.Archived.Has() {
cond = cond.And(builder.Eq{"is_archived": opts.Archived.Value()})
}
if opts.HasMilestones.Has() {
if opts.HasMilestones.Value() {
cond = cond.And(builder.Gt{"num_milestones": 0})
} else {
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
}
}
if opts.OnlyShowRelevant {
// Only show a repo that has at least a topic, an icon, or a description
subQueryCond := builder.NewCond()
// Topic checking. Topics are present.
if setting.Database.Type.IsPostgreSQL() { // postgres stores the topics as json and not as text
subQueryCond = subQueryCond.Or(builder.And(builder.NotNull{"topics"}, builder.Neq{"(topics)::text": "[]"}))
} else {
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
}
// Description checking. Description not empty
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
// Repo has a avatar
subQueryCond = subQueryCond.Or(builder.Neq{"avatar": ""})
// Always hide repo's that are empty
subQueryCond = subQueryCond.And(builder.Eq{"is_empty": false})
cond = cond.And(subQueryCond)
}
return cond
}
// SearchRepository returns repositories based on search options,
// it returns results in given range and number of total results.
func SearchRepository(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
cond := SearchRepositoryCondition(opts)
return SearchRepositoryByCondition(ctx, opts, cond, true)
}
// CountRepository counts repositories based on search options,
func CountRepository(ctx context.Context, opts SearchRepoOptions) (int64, error) {
return db.GetEngine(ctx).Where(SearchRepositoryCondition(opts)).Count(new(Repository))
}
// SearchRepositoryByCondition search repositories by condition
func SearchRepositoryByCondition(ctx context.Context, opts SearchRepoOptions, cond builder.Cond, loadAttributes bool) (RepositoryList, int64, error) {
sess, count, err := searchRepositoryByCondition(ctx, opts, cond)
if err != nil {
return nil, 0, err
}
defaultSize := 50
if opts.PageSize > 0 {
defaultSize = opts.PageSize
}
repos := make(RepositoryList, 0, defaultSize)
if err := sess.Find(&repos); err != nil {
return nil, 0, fmt.Errorf("Repo: %w", err)
}
if opts.PageSize <= 0 {
count = int64(len(repos))
}
if loadAttributes {
if err := repos.LoadAttributes(ctx); err != nil {
return nil, 0, fmt.Errorf("LoadAttributes: %w", err)
}
}
return repos, count, nil
}
func searchRepositoryByCondition(ctx context.Context, opts SearchRepoOptions, cond builder.Cond) (db.Engine, int64, error) {
page := opts.Page
if page <= 0 {
page = 1
}
orderBy := opts.OrderBy
if len(orderBy) == 0 {
orderBy = db.SearchOrderByAlphabetically
}
args := make([]any, 0)
if opts.PriorityOwnerID > 0 {
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s", orderBy))
args = append(args, opts.PriorityOwnerID)
} else if strings.Count(opts.Keyword, "/") == 1 {
// With "owner/repo" search times, prioritise results which match the owner field
orgName := strings.Split(opts.Keyword, "/")[0]
orderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s", orderBy))
args = append(args, orgName)
}
sess := db.GetEngine(ctx)
var count int64
if opts.PageSize > 0 {
var err error
count, err = sess.
Where(cond).
Count(new(Repository))
if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err)
}
}
sess = sess.Where(cond).OrderBy(orderBy.String(), args...)
if opts.PageSize > 0 {
sess = sess.Limit(opts.PageSize, (page-1)*opts.PageSize)
}
return sess, count, nil
}
// SearchRepositoryIDsByCondition search repository IDs by given condition.
func SearchRepositoryIDsByCondition(ctx context.Context, cond builder.Cond) ([]int64, error) {
repoIDs := make([]int64, 0, 10)
return repoIDs, db.GetEngine(ctx).
Table("repository").
Cols("id").
Where(cond).
Find(&repoIDs)
}
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) builder.Cond {
cond := builder.NewCond()
if user == nil || !user.IsRestricted || user.ID <= 0 {
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate}
if user == nil || user.ID <= 0 {
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited)
}
// 1. Be able to see all non-private repositories that either:
cond = cond.Or(builder.And(
builder.Eq{"`repository`.is_private": false},
// 2. Aren't in an private organisation or limited organisation if we're not logged in
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(
builder.And(
builder.Eq{"type": user_model.UserTypeOrganization},
builder.In("visibility", orgVisibilityLimit)),
))))
}
if user != nil {
// 2. Be able to see all repositories that we have unit independent access to
// 3. Be able to see all repositories through team membership(s)
if unitType == unit.TypeInvalid {
// Regardless of UnitType
cond = cond.Or(
UserAccessRepoCond("`repository`.id", user.ID),
UserOrgTeamRepoCond("`repository`.id", user.ID),
)
} else {
// For a specific UnitType
cond = cond.Or(
UserCollaborationRepoCond("`repository`.id", user.ID),
userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType),
)
}
// 4. Repositories that we directly own
cond = cond.Or(builder.Eq{"`repository`.owner_id": user.ID})
if !user.IsRestricted {
// 5. Be able to see all public repos in private organizations that we are an org_user of
cond = cond.Or(userOrgPublicRepoCond(user.ID))
}
}
return cond
}
// SearchRepositoryByName takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
func SearchRepositoryByName(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
opts.IncludeDescription = false
return SearchRepository(ctx, opts)
}
// SearchRepositoryIDs takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
func SearchRepositoryIDs(ctx context.Context, opts SearchRepoOptions) ([]int64, int64, error) {
opts.IncludeDescription = false
cond := SearchRepositoryCondition(opts)
sess, count, err := searchRepositoryByCondition(ctx, opts, cond)
if err != nil {
return nil, 0, err
}
defaultSize := 50
if opts.PageSize > 0 {
defaultSize = opts.PageSize
}
ids := make([]int64, 0, defaultSize)
err = sess.Select("id").Table("repository").Find(&ids)
if opts.PageSize <= 0 {
count = int64(len(ids))
}
return ids, count, err
}
// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered.
func AccessibleRepoIDsQuery(user *user_model.User) *builder.Builder {
// NB: Please note this code needs to still work if user is nil
return builder.Select("id").From("repository").Where(AccessibleRepositoryCondition(user, unit.TypeInvalid))
}
// FindUserCodeAccessibleRepoIDs finds all at Code level accessible repositories' ID by the user's id
func FindUserCodeAccessibleRepoIDs(ctx context.Context, user *user_model.User) ([]int64, error) {
return SearchRepositoryIDsByCondition(ctx, AccessibleRepositoryCondition(user, unit.TypeCode))
}
// FindUserCodeAccessibleOwnerRepoIDs finds all repository IDs for the given owner whose code the user can see.
func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user *user_model.User) ([]int64, error) {
return SearchRepositoryIDsByCondition(ctx, builder.NewCond().And(
builder.Eq{"owner_id": ownerID},
AccessibleRepositoryCondition(user, unit.TypeCode),
))
}
// GetUserRepositories returns a list of repositories of given user.
func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (RepositoryList, int64, error) {
if len(opts.OrderBy) == 0 {
opts.OrderBy = "updated_unix DESC"
}
cond := builder.NewCond()
if opts.Actor == nil {
return nil, 0, util.NewInvalidArgumentErrorf("GetUserRepositories: Actor is needed but not given")
}
cond = cond.And(builder.Eq{"owner_id": opts.Actor.ID})
if !opts.Private {
cond = cond.And(builder.Eq{"is_private": false})
}
if len(opts.LowerNames) > 0 {
cond = cond.And(builder.In("lower_name", opts.LowerNames))
}
sess := db.GetEngine(ctx)
count, err := sess.Where(cond).Count(new(Repository))
if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err)
}
sess = sess.Where(cond).OrderBy(opts.OrderBy.String())
repos := make(RepositoryList, 0, opts.PageSize)
return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos)
}