@@ -7,6 +7,7 @@ import { PrismaClient } from "@sourcebot/db";
7
7
import { processPromiseResults , throwIfAnyFailed } from "./connectionUtils.js" ;
8
8
import * as Sentry from "@sentry/node" ;
9
9
import { env } from "./env.js" ;
10
+ import { log } from "console" ;
10
11
11
12
const logger = createLogger ( 'gitlab' ) ;
12
13
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com" ;
@@ -45,15 +46,27 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
45
46
if ( config . all === true ) {
46
47
if ( hostname !== GITLAB_CLOUD_HOSTNAME ) {
47
48
try {
48
- logger . debug ( `Fetching all projects visible in ${ config . url } ...` ) ;
49
- const { durationMs, data : _projects } = await measure ( async ( ) => {
50
- const fetchFn = ( ) => api . Projects . all ( {
51
- perPage : 100 ,
52
- } ) ;
53
- return fetchWithRetry ( fetchFn , `all projects in ${ config . url } ` , logger ) ;
49
+ // Fetch all groups
50
+ logger . debug ( `Fetching all groups visible in ${ config . url } ...` ) ;
51
+ const { durationMs : groupsDuration , data : _groups } = await measure ( async ( ) => {
52
+ const fetchFn = ( ) => api . Groups . all ( { perPage : 100 , allAvailable : true } ) ;
53
+ return fetchWithRetry ( fetchFn , `all groups in ${ config . url } ` , logger ) ;
54
+ } ) ;
55
+ logger . debug ( `Found ${ _groups . length } groups in ${ groupsDuration } ms.` ) ;
56
+
57
+ config . groups = _groups . map ( g => g . full_path ) ;
58
+
59
+ logger . debug ( `Found these groups: ${ config . groups . join ( '\n' ) } ` ) ;
60
+
61
+ // Fetch all users - too much for sourcebot/gitlab
62
+ logger . debug ( `Fetching all users visible in ${ config . url } ...` ) ;
63
+ const { durationMs : usersDuration , data : _users } = await measure ( async ( ) => {
64
+ const fetchFn = ( ) => api . Users . all ( { perPage : 100 , withoutProjects : false } ) ;
65
+ return fetchWithRetry ( fetchFn , `all users in ${ config . url } ` , logger ) ;
54
66
} ) ;
55
- logger . debug ( `Found ${ _projects . length } projects in ${ durationMs } ms.` ) ;
56
- allRepos = allRepos . concat ( _projects ) ;
67
+ logger . debug ( `Found ${ _users . length } users in ${ usersDuration } ms.` ) ;
68
+
69
+ config . users = _users . map ( u => u . username ) ;
57
70
} catch ( e ) {
58
71
Sentry . captureException ( e ) ;
59
72
logger . error ( `Failed to fetch all projects visible in ${ config . url } .` , e ) ;
@@ -65,76 +78,96 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
65
78
}
66
79
67
80
if ( config . groups ) {
68
- const results = await Promise . allSettled ( config . groups . map ( async ( group ) => {
69
- try {
70
- logger . debug ( `Fetching project info for group ${ group } ...` ) ;
71
- const { durationMs, data } = await measure ( async ( ) => {
72
- const fetchFn = ( ) => api . Groups . allProjects ( group , {
73
- perPage : 100 ,
74
- includeSubgroups : true
81
+ const batchSize = 10 ;
82
+ const allResults = [ ] ;
83
+
84
+ // Process groups in batches of 10
85
+ for ( let i = 0 ; i < config . groups . length ; i += batchSize ) {
86
+ const batch = config . groups . slice ( i , i + batchSize ) ;
87
+ logger . debug ( `Processing batch ${ i / batchSize + 1 } of ${ Math . ceil ( config . groups . length / batchSize ) } (${ batch . length } groups)` ) ;
88
+
89
+ const batchResults = await Promise . allSettled ( batch . map ( async ( group ) => {
90
+ try {
91
+ logger . debug ( `Fetching project info for group ${ group } ...` ) ;
92
+ const { durationMs, data } = await measure ( async ( ) => {
93
+ const fetchFn = ( ) => api . Groups . allProjects ( group , {
94
+ perPage : 100 ,
95
+ includeSubgroups : true
96
+ } ) ;
97
+ return fetchWithRetry ( fetchFn , `group ${ group } ` , logger ) ;
75
98
} ) ;
76
- return fetchWithRetry ( fetchFn , `group ${ group } ` , logger ) ;
77
- } ) ;
78
- logger . debug ( `Found ${ data . length } projects in group ${ group } in ${ durationMs } ms.` ) ;
79
- return {
80
- type : 'valid' as const ,
81
- data
82
- } ;
83
- } catch ( e : any ) {
84
- Sentry . captureException ( e ) ;
85
- logger . error ( `Failed to fetch projects for group ${ group } .` , e ) ;
86
-
87
- const status = e ?. cause ?. response ?. status ;
88
- if ( status === 404 ) {
89
- logger . error ( `Group ${ group } not found or no access` ) ;
99
+ logger . debug ( `Found ${ data . length } projects in group ${ group } in ${ durationMs } ms.` ) ;
90
100
return {
91
- type : 'notFound ' as const ,
92
- value : group
101
+ type : 'valid ' as const ,
102
+ data
93
103
} ;
94
- }
95
- throw e ;
96
- }
97
- } ) ) ;
104
+ } catch ( e : any ) {
105
+ Sentry . captureException ( e ) ;
106
+ logger . error ( `Failed to fetch projects for group ${ group } .` , e ) ;
98
107
99
- throwIfAnyFailed ( results ) ;
100
- const { validItems : validRepos , notFoundItems : notFoundOrgs } = processPromiseResults ( results ) ;
108
+ const status = e ?. cause ?. response ?. status ;
109
+ if ( status === 404 ) {
110
+ logger . error ( `Group ${ group } not found or no access` ) ;
111
+ return {
112
+ type : 'notFound' as const ,
113
+ value : group
114
+ } ;
115
+ }
116
+ throw e ;
117
+ }
118
+ } ) ) ;
119
+ allResults . push ( ...batchResults ) ;
120
+ }
121
+ const { validItems : validRepos , notFoundItems : notFoundOrgs } = processPromiseResults ( allResults ) ;
101
122
allRepos = allRepos . concat ( validRepos ) ;
102
123
notFound . orgs = notFoundOrgs ;
124
+ logger . debug ( `Found ${ validRepos . length } valid repositories in groups.` ) ;
125
+ logger . debug ( `Not found groups: ${ notFoundOrgs . join ( ', ' ) } ` ) ;
126
+ logger . debug ( `These repositories will be downloaded: ${ allRepos . map ( repo => repo . path_with_namespace ) . join ( '\n' ) } ` ) ;
103
127
}
104
128
105
129
if ( config . users ) {
106
- const results = await Promise . allSettled ( config . users . map ( async ( user ) => {
107
- try {
108
- logger . debug ( `Fetching project info for user ${ user } ...` ) ;
109
- const { durationMs, data } = await measure ( async ( ) => {
110
- const fetchFn = ( ) => api . Users . allProjects ( user , {
111
- perPage : 100 ,
130
+ const batchSize = 10 ;
131
+ const allResults = [ ] ;
132
+
133
+ // Process users in batches of 10
134
+ for ( let i = 0 ; i < config . users . length ; i += batchSize ) {
135
+ const batch = config . users . slice ( i , i + batchSize ) ;
136
+ logger . debug ( `Processing batch ${ i / batchSize + 1 } of ${ Math . ceil ( config . users . length / batchSize ) } (${ batch . length } users)` ) ;
137
+
138
+ const batchResults = await Promise . allSettled ( batch . map ( async ( user ) => {
139
+ try {
140
+ logger . debug ( `Fetching project info for user ${ user } ...` ) ;
141
+ const { durationMs, data } = await measure ( async ( ) => {
142
+ const fetchFn = ( ) => api . Users . allProjects ( user , {
143
+ perPage : 100 ,
144
+ } ) ;
145
+ return fetchWithRetry ( fetchFn , `user ${ user } ` , logger ) ;
112
146
} ) ;
113
- return fetchWithRetry ( fetchFn , `user ${ user } ` , logger ) ;
114
- } ) ;
115
- logger . debug ( `Found ${ data . length } projects owned by user ${ user } in ${ durationMs } ms.` ) ;
116
- return {
117
- type : 'valid' as const ,
118
- data
119
- } ;
120
- } catch ( e : any ) {
121
- Sentry . captureException ( e ) ;
122
- logger . error ( `Failed to fetch projects for user ${ user } .` , e ) ;
123
-
124
- const status = e ?. cause ?. response ?. status ;
125
- if ( status === 404 ) {
126
- logger . error ( `User ${ user } not found or no access` ) ;
147
+ logger . debug ( `Found ${ data . length } projects owned by user ${ user } in ${ durationMs } ms.` ) ;
127
148
return {
128
- type : 'notFound ' as const ,
129
- value : user
149
+ type : 'valid ' as const ,
150
+ data
130
151
} ;
131
- }
132
- throw e ;
133
- }
134
- } ) ) ;
152
+ } catch ( e : any ) {
153
+ Sentry . captureException ( e ) ;
154
+ logger . error ( `Failed to fetch projects for user ${ user } .` , e ) ;
135
155
136
- throwIfAnyFailed ( results ) ;
137
- const { validItems : validRepos , notFoundItems : notFoundUsers } = processPromiseResults ( results ) ;
156
+ const status = e ?. cause ?. response ?. status ;
157
+ if ( status === 404 ) {
158
+ logger . error ( `User ${ user } not found or no access` ) ;
159
+ return {
160
+ type : 'notFound' as const ,
161
+ value : user
162
+ } ;
163
+ }
164
+ throw e ;
165
+ }
166
+ } ) ) ;
167
+
168
+ allResults . push ( ...batchResults ) ;
169
+ }
170
+ const { validItems : validRepos , notFoundItems : notFoundUsers } = processPromiseResults ( allResults ) ;
138
171
allRepos = allRepos . concat ( validRepos ) ;
139
172
notFound . users = notFoundUsers ;
140
173
}
@@ -169,7 +202,6 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
169
202
}
170
203
} ) ) ;
171
204
172
- throwIfAnyFailed ( results ) ;
173
205
const { validItems : validRepos , notFoundItems : notFoundRepos } = processPromiseResults ( results ) ;
174
206
allRepos = allRepos . concat ( validRepos ) ;
175
207
notFound . repos = notFoundRepos ;
0 commit comments