2021-12-29 14:02:12 +01:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 19:20:29 +01:00
// SPDX-License-Identifier: MIT
2021-12-29 14:02:12 +01:00
package repo
import (
2022-06-06 10:01:49 +02:00
"context"
"fmt"
"strings"
2021-12-29 14:02:12 +01:00
"code.gitea.io/gitea/models/db"
2022-06-06 10:01:49 +02:00
"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"
2024-02-29 19:52:49 +01:00
"code.gitea.io/gitea/modules/optional"
2023-01-16 12:25:22 +01:00
"code.gitea.io/gitea/modules/setting"
2022-06-06 10:01:49 +02:00
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
2021-12-29 14:02:12 +01:00
2022-06-06 10:01:49 +02:00
"xorm.io/builder"
)
2021-12-29 14:02:12 +01:00
// FindReposMapByIDs find repos as map
2023-10-11 06:24:07 +02:00
func FindReposMapByIDs ( ctx context . Context , repoIDs [ ] int64 , res map [ int64 ] * Repository ) error {
return db . GetEngine ( ctx ) . In ( "id" , repoIDs ) . Find ( & res )
2021-12-29 14:02:12 +01:00
}
2022-06-06 10:01:49 +02:00
// 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 ) )
}
2024-03-12 05:57:19 +01:00
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
}
2023-03-13 12:31:41 +01:00
// LoadAttributes loads the attributes for the given RepositoryList
func ( repos RepositoryList ) LoadAttributes ( ctx context . Context ) error {
2022-06-06 10:01:49 +02:00
if len ( repos ) == 0 {
return nil
}
2022-10-12 07:18:26 +02:00
set := make ( container . Set [ int64 ] )
2022-06-06 10:01:49 +02:00
repoIDs := make ( [ ] int64 , len ( repos ) )
for i := range repos {
2022-10-12 07:18:26 +02:00
set . Add ( repos [ i ] . OwnerID )
2022-06-06 10:01:49 +02:00
repoIDs [ i ] = repos [ i ] . ID
}
// Load owners.
users := make ( map [ int64 ] * user_model . User , len ( set ) )
if err := db . GetEngine ( ctx ) .
Where ( "id > 0" ) .
2022-10-12 07:18:26 +02:00
In ( "id" , set . Values ( ) ) .
2022-06-06 10:01:49 +02:00
Find ( & users ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "find users: %w" , err )
2022-06-06 10:01:49 +02:00
}
for i := range repos {
repos [ i ] . Owner = users [ repos [ i ] . OwnerID ]
}
// Load primary language.
stats := make ( LanguageStatList , 0 , len ( repos ) )
if err := db . GetEngine ( ctx ) .
Where ( "`is_primary` = ? AND `language` != ?" , true , "other" ) .
In ( "`repo_id`" , repoIDs ) .
Find ( & stats ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "find primary languages: %w" , err )
2022-06-06 10:01:49 +02:00
}
stats . LoadAttributes ( )
for i := range repos {
for _ , st := range stats {
if st . RepoID == repos [ i ] . ID {
repos [ i ] . PrimaryLanguage = st
break
}
}
}
return nil
}
// 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
2024-02-29 19:52:49 +01:00
IsPrivate optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
// False -> include just non-collaborative
2024-02-29 19:52:49 +01:00
Collaborate optional . Option [ bool ]
2023-08-23 04:29:17 +02:00
// 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
2022-06-06 10:01:49 +02:00
// None -> include forks AND non-forks
// True -> include just forks
// False -> include just non-forks
2024-02-29 19:52:49 +01:00
Fork optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// None -> include templates AND non-templates
// True -> include just templates
// False -> include just non-templates
2024-02-29 19:52:49 +01:00
Template optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// None -> include mirrors AND non-mirrors
// True -> include just mirrors
// False -> include just non-mirrors
2024-02-29 19:52:49 +01:00
Mirror optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// None -> include archived AND non-archived
// True -> include just archived
// False -> include just non-archived
2024-02-29 19:52:49 +01:00
Archived optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// 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
2024-02-29 19:52:49 +01:00
HasMilestones optional . Option [ bool ]
2022-06-06 10:01:49 +02:00
// LowerNames represents valid lower names to restrict to
LowerNames [ ] string
2022-08-25 20:38:41 +02:00
// 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
2022-06-06 10:01:49 +02:00
}
// SearchOrderBy is used to sort the result
type SearchOrderBy string
func ( s SearchOrderBy ) String ( ) string {
return string ( s )
}
// Strings for sorting result
const (
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
SearchOrderBySize SearchOrderBy = "size ASC"
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
SearchOrderByID SearchOrderBy = "id ASC"
SearchOrderByIDReverse SearchOrderBy = "id DESC"
SearchOrderByStars SearchOrderBy = "num_stars ASC"
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
)
// 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 ,
} ) ,
) ,
)
}
2022-06-16 01:24:10 +02:00
// UserAccessRepoCond returns a condition for selecting all repositories a user has unit independent access to
func UserAccessRepoCond ( idStr string , userID int64 ) builder . Cond {
2022-06-06 10:01:49 +02:00
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 ) } ,
) ) ,
)
}
2022-06-16 01:24:10 +02:00
// 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 {
2022-06-06 10:01:49 +02:00
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" ) .
2022-06-16 01:24:10 +02:00
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 ) )
2022-06-06 10:01:49 +02:00
}
// 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
2022-06-16 01:24:10 +02:00
cond = cond . And ( AccessibleRepositoryCondition ( opts . Actor , unit . TypeInvalid ) )
2022-06-06 10:01:49 +02:00
}
} 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 } ) ,
) ) )
}
2024-02-29 19:52:49 +01:00
if opts . IsPrivate . Has ( ) {
cond = cond . And ( builder . Eq { "is_private" : opts . IsPrivate . Value ( ) } )
2022-06-06 10:01:49 +02:00
}
2024-02-29 19:52:49 +01:00
if opts . Template . Has ( ) {
cond = cond . And ( builder . Eq { "is_template" : opts . Template . Value ( ) } )
2022-06-06 10:01:49 +02:00
}
// 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 ( )
2024-02-29 19:52:49 +01:00
if ! opts . Collaborate . Value ( ) {
2022-06-06 10:01:49 +02:00
accessCond = builder . Eq { "owner_id" : opts . OwnerID }
}
2024-02-29 19:52:49 +01:00
if opts . Collaborate . ValueOrDefault ( true ) {
2022-06-06 10:01:49 +02:00
// A Collaboration is:
2023-08-23 04:29:17 +02:00
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 )
}
2022-06-06 10:01:49 +02:00
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 } ) ) )
}
2024-02-29 19:52:49 +01:00
if opts . Fork . Has ( ) || opts . OnlyShowRelevant {
if opts . OnlyShowRelevant && ! opts . Fork . Has ( ) {
2022-08-25 20:38:41 +02:00
cond = cond . And ( builder . Eq { "is_fork" : false } )
} else {
2024-02-29 19:52:49 +01:00
cond = cond . And ( builder . Eq { "is_fork" : opts . Fork . Value ( ) } )
2022-08-25 20:38:41 +02:00
}
2022-06-06 10:01:49 +02:00
}
2024-02-29 19:52:49 +01:00
if opts . Mirror . Has ( ) {
cond = cond . And ( builder . Eq { "is_mirror" : opts . Mirror . Value ( ) } )
2022-06-06 10:01:49 +02:00
}
if opts . Actor != nil && opts . Actor . IsRestricted {
2022-06-16 01:24:10 +02:00
cond = cond . And ( AccessibleRepositoryCondition ( opts . Actor , unit . TypeInvalid ) )
2022-06-06 10:01:49 +02:00
}
2024-02-29 19:52:49 +01:00
if opts . Archived . Has ( ) {
cond = cond . And ( builder . Eq { "is_archived" : opts . Archived . Value ( ) } )
2022-06-06 10:01:49 +02:00
}
2024-02-29 19:52:49 +01:00
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" } ) )
}
2022-06-06 10:01:49 +02:00
}
2022-08-25 20:38:41 +02:00
if opts . OnlyShowRelevant {
2023-02-04 14:26:38 +01:00
// Only show a repo that has at least a topic, an icon, or a description
2022-08-25 20:38:41 +02:00
subQueryCond := builder . NewCond ( )
2023-01-16 12:25:22 +01:00
// Topic checking. Topics are present.
2023-03-07 11:51:06 +01:00
if setting . Database . Type . IsPostgreSQL ( ) { // postgres stores the topics as json and not as text
2023-01-16 12:25:22 +01:00
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" : "[]" } ) )
}
2022-08-25 20:38:41 +02:00
2023-02-04 14:26:38 +01:00
// Description checking. Description not empty
2022-08-25 20:38:41 +02:00
subQueryCond = subQueryCond . Or ( builder . Neq { "description" : "" } )
2023-02-04 14:26:38 +01:00
// Repo has a avatar
2022-08-25 20:38:41 +02:00
subQueryCond = subQueryCond . Or ( builder . Neq { "avatar" : "" } )
2023-02-04 14:26:38 +01:00
// Always hide repo's that are empty
2022-08-25 20:38:41 +02:00
subQueryCond = subQueryCond . And ( builder . Eq { "is_empty" : false } )
cond = cond . And ( subQueryCond )
}
2022-06-06 10:01:49 +02:00
return cond
}
// SearchRepository returns repositories based on search options,
// it returns results in given range and number of total results.
2022-11-19 09:12:33 +01:00
func SearchRepository ( ctx context . Context , opts * SearchRepoOptions ) ( RepositoryList , int64 , error ) {
2022-06-06 10:01:49 +02:00
cond := SearchRepositoryCondition ( opts )
2022-11-19 09:12:33 +01:00
return SearchRepositoryByCondition ( ctx , opts , cond , true )
2022-06-06 10:01:49 +02:00
}
2023-08-11 19:08:05 +02:00
// 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 ) )
}
2022-06-06 10:01:49 +02:00
// SearchRepositoryByCondition search repositories by condition
2022-11-19 09:12:33 +01:00
func SearchRepositoryByCondition ( ctx context . Context , opts * SearchRepoOptions , cond builder . Cond , loadAttributes bool ) ( RepositoryList , int64 , error ) {
2022-06-06 10:01:49 +02:00
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 {
2022-10-24 21:29:17 +02:00
return nil , 0 , fmt . Errorf ( "Repo: %w" , err )
2022-06-06 10:01:49 +02:00
}
if opts . PageSize <= 0 {
count = int64 ( len ( repos ) )
}
if loadAttributes {
2023-03-13 12:31:41 +01:00
if err := repos . LoadAttributes ( ctx ) ; err != nil {
2022-10-24 21:29:17 +02:00
return nil , 0 , fmt . Errorf ( "LoadAttributes: %w" , err )
2022-06-06 10:01:49 +02:00
}
}
return repos , count , nil
}
func searchRepositoryByCondition ( ctx context . Context , opts * SearchRepoOptions , cond builder . Cond ) ( db . Engine , int64 , error ) {
if opts . Page <= 0 {
opts . Page = 1
}
if len ( opts . OrderBy ) == 0 {
opts . OrderBy = db . SearchOrderByAlphabetically
}
2023-07-04 20:36:08 +02:00
args := make ( [ ] any , 0 )
2022-06-06 10:01:49 +02:00
if opts . PriorityOwnerID > 0 {
opts . OrderBy = db . SearchOrderBy ( fmt . Sprintf ( "CASE WHEN owner_id = ? THEN 0 ELSE owner_id END, %s" , opts . 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 ]
opts . OrderBy = db . SearchOrderBy ( fmt . Sprintf ( "CASE WHEN owner_name LIKE ? THEN 0 ELSE 1 END, %s" , opts . 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 {
2022-10-24 21:29:17 +02:00
return nil , 0 , fmt . Errorf ( "Count: %w" , err )
2022-06-06 10:01:49 +02:00
}
}
sess = sess . Where ( cond ) . OrderBy ( opts . OrderBy . String ( ) , args ... )
if opts . PageSize > 0 {
sess = sess . Limit ( opts . PageSize , ( opts . Page - 1 ) * opts . PageSize )
}
return sess , count , nil
}
2022-10-11 01:12:03 +02:00
// 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 )
}
2022-06-06 10:01:49 +02:00
// AccessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
2022-06-16 01:24:10 +02:00
func AccessibleRepositoryCondition ( user * user_model . User , unitType unit . Type ) builder . Cond {
2022-06-06 10:01:49 +02:00
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 {
2022-06-16 01:24:10 +02:00
// 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 ) ,
)
}
2023-11-14 15:23:04 +01:00
// 4. Repositories that we directly own
cond = cond . Or ( builder . Eq { "`repository`.owner_id" : user . ID } )
if ! user . IsRestricted {
2022-06-06 10:01:49 +02:00
// 5. Be able to see all public repos in private organizations that we are an org_user of
2023-11-14 15:23:04 +01:00
cond = cond . Or ( userOrgPublicRepoCond ( user . ID ) )
}
2022-06-06 10:01:49 +02:00
}
return cond
}
// SearchRepositoryByName takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
2022-11-19 09:12:33 +01:00
func SearchRepositoryByName ( ctx context . Context , opts * SearchRepoOptions ) ( RepositoryList , int64 , error ) {
2022-06-06 10:01:49 +02:00
opts . IncludeDescription = false
2022-11-19 09:12:33 +01:00
return SearchRepository ( ctx , opts )
2022-06-06 10:01:49 +02:00
}
// SearchRepositoryIDs takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
2023-10-11 06:24:07 +02:00
func SearchRepositoryIDs ( ctx context . Context , opts * SearchRepoOptions ) ( [ ] int64 , int64 , error ) {
2022-06-06 10:01:49 +02:00
opts . IncludeDescription = false
cond := SearchRepositoryCondition ( opts )
2023-10-11 06:24:07 +02:00
sess , count , err := searchRepositoryByCondition ( ctx , opts , cond )
2022-06-06 10:01:49 +02:00
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
2022-06-16 01:24:10 +02:00
return builder . Select ( "id" ) . From ( "repository" ) . Where ( AccessibleRepositoryCondition ( user , unit . TypeInvalid ) )
2022-06-06 10:01:49 +02:00
}
2022-06-16 01:24:10 +02:00
// FindUserCodeAccessibleRepoIDs finds all at Code level accessible repositories' ID by the user's id
2022-10-11 01:12:03 +02:00
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 ) ,
) )
2022-06-06 10:01:49 +02:00
}
// GetUserRepositories returns a list of repositories of given user.
2023-10-15 17:46:06 +02:00
func GetUserRepositories ( ctx context . Context , opts * SearchRepoOptions ) ( RepositoryList , int64 , error ) {
2022-06-06 10:01:49 +02:00
if len ( opts . OrderBy ) == 0 {
opts . OrderBy = "updated_unix DESC"
}
cond := builder . NewCond ( )
2022-07-21 23:31:22 +02:00
if opts . Actor == nil {
2022-12-31 12:49:37 +01:00
return nil , 0 , util . NewInvalidArgumentErrorf ( "GetUserRepositories: Actor is needed but not given" )
2022-07-21 23:31:22 +02:00
}
2022-06-06 10:01:49 +02:00
cond = cond . And ( builder . Eq { "owner_id" : opts . Actor . ID } )
if ! opts . Private {
cond = cond . And ( builder . Eq { "is_private" : false } )
}
if opts . LowerNames != nil && len ( opts . LowerNames ) > 0 {
cond = cond . And ( builder . In ( "lower_name" , opts . LowerNames ) )
}
2023-10-15 17:46:06 +02:00
sess := db . GetEngine ( ctx )
2022-06-06 10:01:49 +02:00
count , err := sess . Where ( cond ) . Count ( new ( Repository ) )
if err != nil {
2022-10-24 21:29:17 +02:00
return nil , 0 , fmt . Errorf ( "Count: %w" , err )
2022-06-06 10:01:49 +02:00
}
sess = sess . Where ( cond ) . OrderBy ( opts . OrderBy . String ( ) )
repos := make ( RepositoryList , 0 , opts . PageSize )
return repos , count , db . SetSessionPagination ( sess , opts ) . Find ( & repos )
}