2017-02-27 08:45:03 +08:00
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"strings"
2019-02-19 00:00:27 +08:00
"code.gitea.io/gitea/modules/structs"
2017-10-27 05:16:13 +08:00
"code.gitea.io/gitea/modules/util"
2017-02-27 08:45:03 +08:00
"github.com/go-xorm/builder"
2019-02-19 00:00:27 +08:00
"github.com/go-xorm/core"
2017-02-27 08:45:03 +08:00
)
2017-12-31 22:45:46 +08: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
2017-02-27 08:45:03 +08:00
// RepositoryList contains a list of repositories
type RepositoryList [ ] * Repository
2017-12-04 12:39:01 +08:00
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 ]
}
2017-08-03 13:09:16 +08:00
// RepositoryListOfMap make list from values of map
func RepositoryListOfMap ( repoMap map [ int64 ] * Repository ) RepositoryList {
return RepositoryList ( valuesRepository ( repoMap ) )
}
2017-02-27 08:45:03 +08:00
func ( repos RepositoryList ) loadAttributes ( e Engine ) error {
if len ( repos ) == 0 {
return nil
}
// Load owners.
2017-03-11 16:50:12 +08:00
set := make ( map [ int64 ] struct { } )
2017-02-27 08:45:03 +08:00
for i := range repos {
2017-03-11 16:50:12 +08:00
set [ repos [ i ] . OwnerID ] = struct { } { }
2017-02-27 08:45:03 +08:00
}
2017-03-11 16:50:12 +08:00
users := make ( map [ int64 ] * User , len ( set ) )
2017-02-27 08:45:03 +08:00
if err := e .
Where ( "id > 0" ) .
2017-03-11 16:50:12 +08:00
In ( "id" , keysInt64 ( set ) ) .
2017-02-27 08:45:03 +08:00
Find ( & users ) ; err != nil {
return fmt . Errorf ( "find users: %v" , err )
}
for i := range repos {
2017-03-11 16:50:12 +08:00
repos [ i ] . Owner = users [ repos [ i ] . OwnerID ]
2017-02-27 08:45:03 +08:00
}
return nil
}
// LoadAttributes loads the attributes for the given RepositoryList
func ( repos RepositoryList ) LoadAttributes ( ) error {
return repos . loadAttributes ( x )
}
// MirrorRepositoryList contains the mirror repositories
type MirrorRepositoryList [ ] * Repository
func ( repos MirrorRepositoryList ) loadAttributes ( e Engine ) error {
if len ( repos ) == 0 {
return nil
}
// Load mirrors.
repoIDs := make ( [ ] int64 , 0 , len ( repos ) )
for i := range repos {
if ! repos [ i ] . IsMirror {
continue
}
repoIDs = append ( repoIDs , repos [ i ] . ID )
}
mirrors := make ( [ ] * Mirror , 0 , len ( repoIDs ) )
if err := e .
Where ( "id > 0" ) .
In ( "repo_id" , repoIDs ) .
Find ( & mirrors ) ; err != nil {
return fmt . Errorf ( "find mirrors: %v" , err )
}
set := make ( map [ int64 ] * Mirror )
for i := range mirrors {
set [ mirrors [ i ] . RepoID ] = mirrors [ i ]
}
for i := range repos {
repos [ i ] . Mirror = set [ repos [ i ] . ID ]
}
return nil
}
// LoadAttributes loads the attributes for the given MirrorRepositoryList
func ( repos MirrorRepositoryList ) LoadAttributes ( ) error {
return repos . loadAttributes ( x )
}
// SearchRepoOptions holds the search options
type SearchRepoOptions struct {
2017-10-27 05:16:13 +08:00
Keyword string
OwnerID int64
OrderBy SearchOrderBy
Private bool // Include private repositories in results
Starred bool
Page int
IsProfile bool
AllPublic bool // Include also all public repositories
PageSize int // Can be smaller than or equal to setting.ExplorePagingNum
// None -> include collaborative AND non-collaborative
// True -> include just collaborative
// False -> incude just non-collaborative
Collaborate util . OptionalBool
// None -> include forks AND non-forks
// True -> include just forks
// False -> include just non-forks
Fork util . OptionalBool
// None -> include mirrors AND non-mirrors
// True -> include just mirrors
// False -> include just non-mirrors
Mirror util . OptionalBool
2018-09-13 10:33:48 +08:00
// only search topic name
TopicOnly bool
2017-02-27 08:45:03 +08:00
}
2017-09-22 20:53:21 +08: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 = "name DESC"
SearchOrderByLeastUpdated = "updated_unix ASC"
SearchOrderByRecentUpdated = "updated_unix DESC"
SearchOrderByOldest = "created_unix ASC"
SearchOrderByNewest = "created_unix DESC"
SearchOrderBySize = "size ASC"
SearchOrderBySizeReverse = "size DESC"
2017-10-11 04:37:18 +08:00
SearchOrderByID = "id ASC"
SearchOrderByIDReverse = "id DESC"
2018-05-24 09:03:42 +08:00
SearchOrderByStars = "num_stars ASC"
SearchOrderByStarsReverse = "num_stars DESC"
SearchOrderByForks = "num_forks ASC"
SearchOrderByForksReverse = "num_forks DESC"
2017-09-22 20:53:21 +08:00
)
2017-02-27 08:45:03 +08:00
// SearchRepositoryByName takes keyword and part of repository name to search,
// it returns results in given range and number of total results.
2017-10-17 23:20:22 +08:00
func SearchRepositoryByName ( opts * SearchRepoOptions ) ( RepositoryList , int64 , error ) {
2017-02-27 08:45:03 +08:00
if opts . Page <= 0 {
opts . Page = 1
}
2017-10-17 23:20:22 +08:00
var cond = builder . NewCond ( )
2017-08-24 22:01:03 +08:00
2017-10-17 23:20:22 +08:00
if ! opts . Private {
cond = cond . And ( builder . Eq { "is_private" : false } )
2019-02-19 00:00:27 +08:00
accessCond := builder . Or (
builder . NotIn ( "owner_id" , builder . Select ( "id" ) . From ( "`user`" ) . Where ( builder . Or ( builder . Eq { "visibility" : structs . VisibleTypeLimited } , builder . Eq { "visibility" : structs . VisibleTypePrivate } ) ) ) ,
builder . NotIn ( "owner_id" , builder . Select ( "id" ) . From ( "`user`" ) . Where ( builder . Eq { "type" : UserTypeOrganization } ) ) )
cond = cond . And ( accessCond )
2017-10-17 23:20:22 +08:00
}
2017-08-24 22:01:03 +08:00
2017-10-17 23:20:22 +08:00
if opts . OwnerID > 0 {
if opts . Starred {
2018-10-31 05:48:37 +08:00
cond = cond . And ( builder . In ( "id" , builder . Select ( "repo_id" ) . From ( "star" ) . Where ( builder . Eq { "uid" : opts . OwnerID } ) ) )
2017-10-17 23:20:22 +08:00
} else {
2017-10-27 05:16:13 +08:00
var accessCond = builder . NewCond ( )
if opts . Collaborate != util . OptionalBoolTrue {
accessCond = builder . Eq { "owner_id" : opts . OwnerID }
}
2017-08-24 22:01:03 +08:00
2017-10-27 05:16:13 +08:00
if opts . Collaborate != util . OptionalBoolFalse {
2017-10-17 23:20:22 +08:00
collaborateCond := builder . And (
2018-09-13 10:33:48 +08:00
builder . Expr ( "repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)" , opts . OwnerID ) ,
2017-10-17 23:20:22 +08:00
builder . Neq { "owner_id" : opts . OwnerID } )
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 )
2017-08-24 22:01:03 +08:00
}
2019-02-19 00:00:27 +08:00
var exprCond builder . Cond
if DbCfg . Type == core . POSTGRES {
exprCond = builder . Expr ( "org_user.org_id = \"user\".id" )
} else if DbCfg . Type == core . MSSQL {
exprCond = builder . Expr ( "org_user.org_id = [user].id" )
} else {
exprCond = builder . Eq { "org_user.org_id" : "user.id" }
}
visibilityCond := builder . Or (
builder . In ( "owner_id" ,
builder . Select ( "org_id" ) . From ( "org_user" ) .
LeftJoin ( "`user`" , exprCond ) .
Where (
builder . And (
builder . Eq { "uid" : opts . OwnerID } ,
builder . Eq { "visibility" : structs . VisibleTypePrivate } ) ) ,
) ,
builder . In ( "owner_id" ,
builder . Select ( "id" ) . From ( "`user`" ) .
Where (
builder . Or (
builder . Eq { "visibility" : structs . VisibleTypePublic } ,
builder . Eq { "visibility" : structs . VisibleTypeLimited } ) ) ,
) ,
builder . NotIn ( "owner_id" , builder . Select ( "id" ) . From ( "`user`" ) . Where ( builder . Eq { "type" : UserTypeOrganization } ) ) ,
)
cond = cond . And ( visibilityCond )
2017-10-27 05:16:13 +08:00
if opts . AllPublic {
accessCond = accessCond . Or ( builder . Eq { "is_private" : false } )
}
2017-10-17 23:20:22 +08:00
cond = cond . And ( accessCond )
}
2017-02-27 08:45:03 +08:00
}
2017-10-11 04:37:18 +08:00
if opts . Keyword != "" {
2018-10-18 11:14:28 +08:00
// separate keyword
2018-10-31 05:48:37 +08:00
var subQueryCond = builder . NewCond ( )
2018-10-18 11:14:28 +08:00
for _ , v := range strings . Split ( opts . Keyword , "," ) {
2018-10-31 05:48:37 +08:00
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" )
var keywordCond = builder . In ( "id" , subQuery )
if ! opts . TopicOnly {
var likes = builder . NewCond ( )
for _ , v := range strings . Split ( opts . Keyword , "," ) {
likes = likes . Or ( builder . Like { "lower_name" , strings . ToLower ( v ) } )
2018-10-18 11:14:28 +08:00
}
2018-10-31 05:48:37 +08:00
keywordCond = keywordCond . Or ( likes )
2018-09-13 10:33:48 +08:00
}
cond = cond . And ( keywordCond )
2017-10-11 04:37:18 +08:00
}
2017-10-27 05:16:13 +08:00
if opts . Fork != util . OptionalBoolNone {
cond = cond . And ( builder . Eq { "is_fork" : opts . Fork == util . OptionalBoolTrue } )
}
if opts . Mirror != util . OptionalBoolNone {
cond = cond . And ( builder . Eq { "is_mirror" : opts . Mirror == util . OptionalBoolTrue } )
}
2017-02-27 08:45:03 +08:00
if len ( opts . OrderBy ) == 0 {
2017-09-22 20:53:21 +08:00
opts . OrderBy = SearchOrderByAlphabetically
2017-02-27 08:45:03 +08:00
}
2017-08-23 09:30:54 +08:00
sess := x . NewSession ( )
defer sess . Close ( )
2017-10-17 23:20:22 +08:00
count , err := sess .
Where ( cond ) .
Count ( new ( Repository ) )
2018-09-13 10:33:48 +08:00
2017-10-17 23:20:22 +08:00
if err != nil {
return nil , 0 , fmt . Errorf ( "Count: %v" , err )
}
repos := make ( RepositoryList , 0 , opts . PageSize )
2017-02-27 08:45:03 +08:00
if err = sess .
2017-08-23 09:30:54 +08:00
Where ( cond ) .
2018-10-31 05:48:37 +08:00
OrderBy ( opts . OrderBy . String ( ) ) .
2017-02-27 08:45:03 +08:00
Limit ( opts . PageSize , ( opts . Page - 1 ) * opts . PageSize ) .
Find ( & repos ) ; err != nil {
return nil , 0 , fmt . Errorf ( "Repo: %v" , err )
}
if ! opts . IsProfile {
2017-08-23 09:30:54 +08:00
if err = repos . loadAttributes ( sess ) ; err != nil {
2017-02-27 08:45:03 +08:00
return nil , 0 , fmt . Errorf ( "LoadAttributes: %v" , err )
}
}
2017-10-17 23:20:22 +08:00
return repos , count , nil
2017-02-27 08:45:03 +08:00
}
2018-03-16 22:04:33 +08:00
// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id
func FindUserAccessibleRepoIDs ( userID int64 ) ( [ ] int64 , error ) {
var accessCond builder . Cond = builder . Eq { "is_private" : false }
if userID > 0 {
accessCond = accessCond . Or (
builder . Eq { "owner_id" : userID } ,
builder . And (
builder . Expr ( "id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)" , userID ) ,
builder . Neq { "owner_id" : userID } ,
) ,
)
}
repoIDs := make ( [ ] int64 , 0 , 10 )
if err := x .
Table ( "repository" ) .
Cols ( "id" ) .
Where ( accessCond ) .
Find ( & repoIDs ) ; err != nil {
return nil , fmt . Errorf ( "FindUserAccesibleRepoIDs: %v" , err )
}
return repoIDs , nil
}