@@ -7,6 +7,7 @@ import { PrismaClient } from "@sourcebot/db";
77import { processPromiseResults , throwIfAnyFailed } from "./connectionUtils.js" ;
88import * as Sentry from "@sentry/node" ;
99import { env } from "./env.js" ;
10+ import { log } from "console" ;
1011
1112const logger = createLogger ( 'gitlab' ) ;
1213export const GITLAB_CLOUD_HOSTNAME = "gitlab.com" ;
@@ -45,15 +46,27 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
4546 if ( config . all === true ) {
4647 if ( hostname !== GITLAB_CLOUD_HOSTNAME ) {
4748 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 ) ;
5466 } ) ;
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 ) ;
5770 } catch ( e ) {
5871 Sentry . captureException ( e ) ;
5972 logger . error ( `Failed to fetch all projects visible in ${ config . url } .` , e ) ;
@@ -65,76 +78,96 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
6578 }
6679
6780 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 ) ;
7598 } ) ;
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.` ) ;
90100 return {
91- type : 'notFound ' as const ,
92- value : group
101+ type : 'valid ' as const ,
102+ data
93103 } ;
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 ) ;
98107
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 ) ;
101122 allRepos = allRepos . concat ( validRepos ) ;
102123 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' ) } ` ) ;
103127 }
104128
105129 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 ) ;
112146 } ) ;
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.` ) ;
127148 return {
128- type : 'notFound ' as const ,
129- value : user
149+ type : 'valid ' as const ,
150+ data
130151 } ;
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 ) ;
135155
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 ) ;
138171 allRepos = allRepos . concat ( validRepos ) ;
139172 notFound . users = notFoundUsers ;
140173 }
@@ -169,7 +202,6 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
169202 }
170203 } ) ) ;
171204
172- throwIfAnyFailed ( results ) ;
173205 const { validItems : validRepos , notFoundItems : notFoundRepos } = processPromiseResults ( results ) ;
174206 allRepos = allRepos . concat ( validRepos ) ;
175207 notFound . repos = notFoundRepos ;
0 commit comments