@@ -12,6 +12,7 @@ import {
12
12
import { SchemaRestRepository as ServerRepository } from "@coderabbitai/bitbucket/server/openapi" ;
13
13
import { processPromiseResults } from "./connectionUtils.js" ;
14
14
import { throwIfAnyFailed } from "./connectionUtils.js" ;
15
+ import { PaginatedResponse } from "@gitbeaker/rest" ;
15
16
16
17
const logger = createLogger ( "Bitbucket" ) ;
17
18
const BITBUCKET_CLOUD_GIT = 'https://bitbucket.org' ;
@@ -27,7 +28,6 @@ interface BitbucketClient {
27
28
apiClient : any ;
28
29
baseUrl : string ;
29
30
gitUrl : string ;
30
- getPaginated : < T , V extends CloudGetRequestPath | ServerGetRequestPath > ( path : V , get : ( url : V ) => Promise < PaginatedResponse < T > > ) => Promise < T [ ] > ;
31
31
getReposForWorkspace : ( client : BitbucketClient , workspaces : string [ ] ) => Promise < { validRepos : BitbucketRepository [ ] , notFoundWorkspaces : string [ ] } > ;
32
32
getReposForProjects : ( client : BitbucketClient , projects : string [ ] ) => Promise < { validRepos : BitbucketRepository [ ] , notFoundProjects : string [ ] } > ;
33
33
getRepos : ( client : BitbucketClient , repos : string [ ] ) => Promise < { validRepos : BitbucketRepository [ ] , notFoundRepos : string [ ] } > ;
@@ -40,7 +40,7 @@ type CloudGetRequestPath = ClientPathsWithMethod<CloudAPI, "get">;
40
40
type ServerAPI = ReturnType < typeof createBitbucketServerClient > ;
41
41
type ServerGetRequestPath = ClientPathsWithMethod < ServerAPI , "get" > ;
42
42
43
- type PaginatedResponse < T > = {
43
+ type CloudPaginatedResponse < T > = {
44
44
readonly next ?: string ;
45
45
readonly page ?: number ;
46
46
readonly pagelen ?: number ;
@@ -49,6 +49,15 @@ type PaginatedResponse<T> = {
49
49
readonly values ?: readonly T [ ] ;
50
50
}
51
51
52
+ type ServerPaginatedResponse < T > = {
53
+ readonly size : number ;
54
+ readonly limit : number ;
55
+ readonly isLastPage : boolean ;
56
+ readonly values : readonly T [ ] ;
57
+ readonly start : number ;
58
+ readonly nextPageStart : number ;
59
+ }
60
+
52
61
export const getBitbucketReposFromConfig = async ( config : BitbucketConnectionConfig , orgId : number , db : PrismaClient ) => {
53
62
const token = config . token ?
54
63
await getTokenFromConfig ( config . token , orgId , db , logger ) :
@@ -82,7 +91,7 @@ export const getBitbucketReposFromConfig = async (config: BitbucketConnectionCon
82
91
if ( config . projects ) {
83
92
const { validRepos, notFoundProjects } = await client . getReposForProjects ( client , config . projects ) ;
84
93
allRepos = allRepos . concat ( validRepos ) ;
85
- notFound . repos = notFoundProjects ;
94
+ notFound . orgs = notFoundProjects ;
86
95
}
87
96
88
97
if ( config . repos ) {
@@ -103,12 +112,18 @@ export const getBitbucketReposFromConfig = async (config: BitbucketConnectionCon
103
112
104
113
function cloudClient ( user : string | undefined , token : string | undefined ) : BitbucketClient {
105
114
106
- const authorizationString = ! user || user == "x-token-auth" ? `Bearer ${ token } ` : `Basic ${ Buffer . from ( `${ user } :${ token } ` ) . toString ( 'base64' ) } ` ;
115
+ const authorizationString =
116
+ token
117
+ ? ! user || user == "x-token-auth"
118
+ ? `Bearer ${ token } `
119
+ : `Basic ${ Buffer . from ( `${ user } :${ token } ` ) . toString ( 'base64' ) } `
120
+ : undefined ;
121
+
107
122
const clientOptions : ClientOptions = {
108
123
baseUrl : BITBUCKET_CLOUD_API ,
109
124
headers : {
110
125
Accept : "application/json" ,
111
- Authorization : authorizationString ,
126
+ ... ( authorizationString ? { Authorization : authorizationString } : { } ) ,
112
127
} ,
113
128
} ;
114
129
@@ -119,7 +134,6 @@ function cloudClient(user: string | undefined, token: string | undefined): Bitbu
119
134
apiClient : apiClient ,
120
135
baseUrl : BITBUCKET_CLOUD_API ,
121
136
gitUrl : BITBUCKET_CLOUD_GIT ,
122
- getPaginated : getPaginatedCloud ,
123
137
getReposForWorkspace : cloudGetReposForWorkspace ,
124
138
getReposForProjects : cloudGetReposForProjects ,
125
139
getRepos : cloudGetRepos ,
@@ -133,7 +147,10 @@ function cloudClient(user: string | undefined, token: string | undefined): Bitbu
133
147
* We need to do `V extends CloudGetRequestPath` since we will need to call `apiClient.GET(url, ...)`, which
134
148
* expects `url` to be of type `CloudGetRequestPath`. See example.
135
149
**/
136
- const getPaginatedCloud = async < T , V extends CloudGetRequestPath | ServerGetRequestPath > ( path : V , get : ( url : V ) => Promise < PaginatedResponse < T > > ) => {
150
+ const getPaginatedCloud = async < T > (
151
+ path : CloudGetRequestPath ,
152
+ get : ( url : CloudGetRequestPath ) => Promise < CloudPaginatedResponse < T > >
153
+ ) : Promise < T [ ] > => {
137
154
const results : T [ ] = [ ] ;
138
155
let url = path ;
139
156
@@ -150,8 +167,7 @@ const getPaginatedCloud = async <T, V extends CloudGetRequestPath | ServerGetReq
150
167
break ;
151
168
}
152
169
153
- // cast required here since response.next is a string.
154
- url = response . next as V ;
170
+ url = response . next as CloudGetRequestPath ;
155
171
}
156
172
return results ;
157
173
}
@@ -164,7 +180,7 @@ async function cloudGetReposForWorkspace(client: BitbucketClient, workspaces: st
164
180
165
181
const path = `/repositories/${ workspace } ` as CloudGetRequestPath ;
166
182
const { durationMs, data } = await measure ( async ( ) => {
167
- const fetchFn = ( ) => client . getPaginated < CloudRepository , typeof path > ( path , async ( url ) => {
183
+ const fetchFn = ( ) => getPaginatedCloud < CloudRepository > ( path , async ( url ) => {
168
184
const response = await client . apiClient . GET ( url , {
169
185
params : {
170
186
path : {
@@ -223,9 +239,15 @@ async function cloudGetReposForProjects(client: BitbucketClient, projects: strin
223
239
224
240
logger . debug ( `Fetching all repos for project ${ project } for workspace ${ workspace } ...` ) ;
225
241
try {
226
- const path = `/repositories/${ workspace } ?q=project.key="${ project_name } "` as CloudGetRequestPath ;
227
- const repos = await client . getPaginated < CloudRepository , typeof path > ( path , async ( url ) => {
228
- const response = await client . apiClient . GET ( url ) ;
242
+ const path = `/repositories/${ workspace } ` as CloudGetRequestPath ;
243
+ const repos = await getPaginatedCloud < CloudRepository > ( path , async ( url ) => {
244
+ const response = await client . apiClient . GET ( url , {
245
+ params : {
246
+ query : {
247
+ q : `project.key="${ project_name } "`
248
+ }
249
+ }
250
+ } ) ;
229
251
const { data, error } = response ;
230
252
if ( error ) {
231
253
throw new Error ( `Failed to fetch projects for workspace ${ workspace } : ${ error . type } ` ) ;
@@ -317,6 +339,10 @@ function cloudShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketConn
317
339
return true ;
318
340
}
319
341
342
+ if ( ! ! config . exclude ?. archived ) {
343
+ logger . warn ( `Exclude archived repos flag provided in config but Bitbucket Cloud does not support archived repos. Ignoring...` ) ;
344
+ }
345
+
320
346
if ( ! ! config . exclude ?. forks && cloudRepo . parent !== undefined ) {
321
347
return true ;
322
348
}
@@ -358,7 +384,6 @@ function serverClient(url: string, user: string | undefined, token: string | und
358
384
apiClient : apiClient ,
359
385
baseUrl : url ,
360
386
gitUrl : url ,
361
- getPaginated : getPaginatedServer ,
362
387
getReposForWorkspace : serverGetReposForWorkspace ,
363
388
getReposForProjects : serverGetReposForProjects ,
364
389
getRepos : serverGetRepos ,
@@ -368,25 +393,27 @@ function serverClient(url: string, user: string | undefined, token: string | und
368
393
return client ;
369
394
}
370
395
371
- const getPaginatedServer = async < T , V extends CloudGetRequestPath | ServerGetRequestPath > ( path : V , get : ( url : V ) => Promise < PaginatedResponse < T > > ) => {
396
+ const getPaginatedServer = async < T > (
397
+ path : ServerGetRequestPath ,
398
+ get : ( url : ServerGetRequestPath , start ?: number ) => Promise < ServerPaginatedResponse < T > >
399
+ ) : Promise < T [ ] > => {
372
400
const results : T [ ] = [ ] ;
373
- let url = path ;
401
+ let nextStart : number | undefined ;
374
402
375
403
while ( true ) {
376
- const response = await get ( url ) ;
404
+ const response = await get ( path , nextStart ) ;
377
405
378
406
if ( ! response . values || response . values . length === 0 ) {
379
407
break ;
380
408
}
381
409
382
410
results . push ( ...response . values ) ;
383
411
384
- if ( ! response . next ) {
412
+ if ( response . isLastPage ) {
385
413
break ;
386
414
}
387
415
388
- // cast required here since response.next is a string.
389
- url = response . next as V ;
416
+ nextStart = response . nextPageStart ;
390
417
}
391
418
return results ;
392
419
}
@@ -406,8 +433,14 @@ async function serverGetReposForProjects(client: BitbucketClient, projects: stri
406
433
407
434
const path = `/rest/api/1.0/projects/${ project } /repos` as ServerGetRequestPath ;
408
435
const { durationMs, data } = await measure ( async ( ) => {
409
- const fetchFn = ( ) => client . getPaginated < ServerRepository , typeof path > ( path , async ( url ) => {
410
- const response = await client . apiClient . GET ( url ) ;
436
+ const fetchFn = ( ) => getPaginatedServer < ServerRepository > ( path , async ( url , start ) => {
437
+ const response = await client . apiClient . GET ( url , {
438
+ params : {
439
+ query : {
440
+ start,
441
+ }
442
+ }
443
+ } ) ;
411
444
const { data, error } = response ;
412
445
if ( error ) {
413
446
throw new Error ( `Failed to fetch repos for project ${ project } : ${ JSON . stringify ( error ) } ` ) ;
@@ -504,8 +537,10 @@ function serverShouldExcludeRepo(repo: BitbucketRepository, config: BitbucketCon
504
537
return true ;
505
538
}
506
539
507
- // Note: Bitbucket Server doesn't have a direct way to check if a repo is a fork
508
- // We'll need to check the origin property if it exists
540
+ if ( ! ! config . exclude ?. archived && serverRepo . archived ) {
541
+ return true ;
542
+ }
543
+
509
544
if ( ! ! config . exclude ?. forks && serverRepo . origin !== undefined ) {
510
545
return true ;
511
546
}
0 commit comments