1+ import axios , { AxiosInstance } from 'axios' ;
2+ import {
3+ BaseProviderAdapter ,
4+ ProviderCapabilities ,
5+ ProviderOperationResult ,
6+ RepositoryInfo ,
7+ BranchInfo ,
8+ CommitInfo ,
9+ WebhookConfig ,
10+ ProviderAdapterFactory ,
11+ } from './base' ;
12+
13+ export class CustomGitAdapter extends BaseProviderAdapter {
14+ private client : AxiosInstance ;
15+
16+ constructor ( name : string , endpoint : string , credentials : Record < string , any > ) {
17+ super ( name , endpoint , credentials ) ;
18+
19+ this . client = axios . create ( {
20+ baseURL : this . endpoint ,
21+ headers : this . getAuthHeaders ( ) ,
22+ timeout : 30000 ,
23+ } ) ;
24+ }
25+
26+ getCapabilities ( ) : ProviderCapabilities {
27+ // Custom Git servers typically have limited capabilities
28+ // These can be overridden via configuration
29+ return {
30+ supportsWebhooks : false ,
31+ supportsPullRequests : false ,
32+ supportsIssues : false ,
33+ supportsProjects : false ,
34+ supportsWiki : false ,
35+ supportsPackages : false ,
36+ supportsActions : false ,
37+ supportsProtectedBranches : false ,
38+ maxFileSize : 100 , // 100 MB default
39+ maxRepoSize : 10 * 1024 , // 10 GB default
40+ } ;
41+ }
42+
43+ protected getAuthHeaders ( ) : Record < string , string > {
44+ const headers : Record < string , string > = {
45+ 'Content-Type' : 'application/json' ,
46+ } ;
47+
48+ // Support various authentication methods
49+ if ( this . credentials . token ) {
50+ // Bearer token authentication
51+ headers [ 'Authorization' ] = `Bearer ${ this . credentials . token } ` ;
52+ } else if ( this . credentials . username && this . credentials . password ) {
53+ // Basic authentication
54+ const auth = Buffer . from ( `${ this . credentials . username } :${ this . credentials . password } ` ) . toString ( 'base64' ) ;
55+ headers [ 'Authorization' ] = `Basic ${ auth } ` ;
56+ } else if ( this . credentials . apiKey ) {
57+ // API key authentication (custom header)
58+ headers [ 'X-API-Key' ] = this . credentials . apiKey ;
59+ }
60+
61+ return headers ;
62+ }
63+
64+ async validateCredentials ( ) : Promise < ProviderOperationResult < boolean > > {
65+ try {
66+ // For custom Git servers, we'll try to access the info/refs endpoint
67+ // This is a standard Git HTTP endpoint
68+ const testRepo = this . credentials . testRepository || 'test.git' ;
69+ const response = await this . client . get ( `/${ testRepo } /info/refs` , {
70+ params : { service : 'git-upload-pack' } ,
71+ validateStatus : ( status ) => status < 500 ,
72+ } ) ;
73+
74+ if ( response . status === 401 || response . status === 403 ) {
75+ return {
76+ success : false ,
77+ error : 'Invalid credentials' ,
78+ } ;
79+ }
80+
81+ return {
82+ success : true ,
83+ data : true ,
84+ details : {
85+ authenticated : response . status !== 401 ,
86+ serverType : 'custom' ,
87+ } ,
88+ } ;
89+ } catch ( error ) {
90+ return this . handleError ( error ) ;
91+ }
92+ }
93+
94+ async getRepository ( owner : string , repo : string ) : Promise < ProviderOperationResult < RepositoryInfo > > {
95+ try {
96+ // For custom Git servers, we have limited information
97+ // We'll construct what we can from the available data
98+ const repoPath = `${ owner } /${ repo } .git` ;
99+ const fullUrl = `${ this . endpoint } /${ repoPath } ` ;
100+
101+ // Try to get refs to verify repository exists
102+ const response = await this . client . get ( `/${ repoPath } /info/refs` , {
103+ params : { service : 'git-upload-pack' } ,
104+ validateStatus : ( status ) => status < 500 ,
105+ } ) ;
106+
107+ if ( response . status === 404 ) {
108+ return {
109+ success : false ,
110+ error : 'Repository not found' ,
111+ } ;
112+ }
113+
114+ // Parse refs to find default branch
115+ const refs = this . parseRefs ( response . data ) ;
116+ const defaultBranch = this . findDefaultBranch ( refs ) ;
117+
118+ const repoInfo : RepositoryInfo = {
119+ name : repo ,
120+ fullName : `${ owner } /${ repo } ` ,
121+ description : undefined , // Not available in basic Git
122+ private : true , // Assume private by default
123+ defaultBranch : defaultBranch || 'master' ,
124+ cloneUrl : fullUrl ,
125+ sshUrl : fullUrl . replace ( / ^ h t t p s ? : \/ \/ / , 'git@' ) . replace ( / \/ / , ':' ) ,
126+ webUrl : fullUrl ,
127+ size : undefined , // Not available
128+ createdAt : new Date ( ) , // Not available, using current date
129+ updatedAt : new Date ( ) , // Not available, using current date
130+ language : undefined , // Not available
131+ topics : [ ] , // Not available
132+ } ;
133+
134+ return { success : true , data : repoInfo } ;
135+ } catch ( error ) {
136+ return this . handleError ( error ) ;
137+ }
138+ }
139+
140+ async listBranches ( owner : string , repo : string ) : Promise < ProviderOperationResult < BranchInfo [ ] > > {
141+ try {
142+ const repoPath = `${ owner } /${ repo } .git` ;
143+ const response = await this . client . get ( `/${ repoPath } /info/refs` , {
144+ params : { service : 'git-upload-pack' } ,
145+ } ) ;
146+
147+ const refs = this . parseRefs ( response . data ) ;
148+ const branches : BranchInfo [ ] = [ ] ;
149+
150+ for ( const [ ref , sha ] of Object . entries ( refs ) ) {
151+ if ( ref . startsWith ( 'refs/heads/' ) ) {
152+ branches . push ( {
153+ name : ref . replace ( 'refs/heads/' , '' ) ,
154+ commit : sha ,
155+ protected : false , // Not available in basic Git
156+ } ) ;
157+ }
158+ }
159+
160+ return { success : true , data : branches } ;
161+ } catch ( error ) {
162+ return this . handleError ( error ) ;
163+ }
164+ }
165+
166+ async getCommit ( owner : string , repo : string , sha : string ) : Promise < ProviderOperationResult < CommitInfo > > {
167+ try {
168+ // Custom Git servers don't typically expose commit details via HTTP
169+ // We'll return a minimal commit info
170+ const repoPath = `${ owner } /${ repo } .git` ;
171+
172+ // We can only verify the commit exists by checking refs
173+ const commitInfo : CommitInfo = {
174+ sha : sha ,
175+ message : 'Commit details not available via HTTP' ,
176+ author : {
177+ name : 'Unknown' ,
178+ email : 'unknown@example.com' ,
179+ date : new Date ( ) ,
180+ } ,
181+ committer : {
182+ name : 'Unknown' ,
183+ email : 'unknown@example.com' ,
184+ date : new Date ( ) ,
185+ } ,
186+ parents : [ ] ,
187+ url : `${ this . endpoint } /${ repoPath } /commit/${ sha } ` ,
188+ } ;
189+
190+ return { success : true , data : commitInfo } ;
191+ } catch ( error ) {
192+ return this . handleError ( error ) ;
193+ }
194+ }
195+
196+ async push (
197+ owner : string ,
198+ repo : string ,
199+ branch : string ,
200+ files : Array < { path : string ; content : string ; encoding ?: string } > ,
201+ message : string ,
202+ author ?: { name : string ; email : string }
203+ ) : Promise < ProviderOperationResult < CommitInfo > > {
204+ // Basic Git HTTP protocol doesn't support file-level operations
205+ // This would require implementing the Git pack protocol
206+ return {
207+ success : false ,
208+ error : 'Push operations are not supported for custom Git servers via HTTP. Please use Git CLI or SSH.' ,
209+ } ;
210+ }
211+
212+ async pull ( owner : string , repo : string , branch : string ) : Promise < ProviderOperationResult < any > > {
213+ try {
214+ const repoPath = `${ owner } /${ repo } .git` ;
215+
216+ // Get refs
217+ const refsResponse = await this . client . get ( `/${ repoPath } /info/refs` , {
218+ params : { service : 'git-upload-pack' } ,
219+ } ) ;
220+
221+ const refs = this . parseRefs ( refsResponse . data ) ;
222+ const branchRef = `refs/heads/${ branch } ` ;
223+ const commitSha = refs [ branchRef ] ;
224+
225+ if ( ! commitSha ) {
226+ return {
227+ success : false ,
228+ error : `Branch '${ branch } ' not found` ,
229+ } ;
230+ }
231+
232+ return {
233+ success : true ,
234+ data : {
235+ commit : commitSha ,
236+ branch : {
237+ name : branch ,
238+ commit : commitSha ,
239+ } ,
240+ refs : refs ,
241+ } ,
242+ } ;
243+ } catch ( error ) {
244+ return this . handleError ( error ) ;
245+ }
246+ }
247+
248+ async createWebhook ( owner : string , repo : string , config : WebhookConfig ) : Promise < ProviderOperationResult < any > > {
249+ return {
250+ success : false ,
251+ error : 'Webhooks are not supported for custom Git servers' ,
252+ } ;
253+ }
254+
255+ async deleteWebhook ( owner : string , repo : string , webhookId : string ) : Promise < ProviderOperationResult < boolean > > {
256+ return {
257+ success : false ,
258+ error : 'Webhooks are not supported for custom Git servers' ,
259+ } ;
260+ }
261+
262+ async getHealth ( ) : Promise < ProviderOperationResult < { healthy : boolean ; latency : number ; details ?: any } > > {
263+ try {
264+ const start = Date . now ( ) ;
265+
266+ // Try to access the base endpoint
267+ const response = await this . client . get ( '/' , {
268+ validateStatus : ( status ) => status < 500 ,
269+ } ) ;
270+
271+ const latency = Date . now ( ) - start ;
272+ const healthy = response . status < 400 ;
273+
274+ return {
275+ success : true ,
276+ data : {
277+ healthy,
278+ latency,
279+ details : {
280+ status : response . status ,
281+ serverType : 'custom' ,
282+ } ,
283+ } ,
284+ } ;
285+ } catch ( error ) {
286+ return {
287+ success : false ,
288+ data : {
289+ healthy : false ,
290+ latency : - 1 ,
291+ } ,
292+ error : this . handleError ( error ) . error ,
293+ } ;
294+ }
295+ }
296+
297+ // Helper methods specific to custom Git servers
298+ private parseRefs ( data : string ) : Record < string , string > {
299+ const refs : Record < string , string > = { } ;
300+ const lines = data . split ( '\n' ) ;
301+
302+ for ( const line of lines ) {
303+ // Skip empty lines and service advertisements
304+ if ( ! line || line . startsWith ( '#' ) ) continue ;
305+
306+ // Parse ref format: "SHA ref-name"
307+ const match = line . match ( / ^ ( [ 0 - 9 a - f ] { 40 } ) \s + ( .+ ) $ / ) ;
308+ if ( match ) {
309+ refs [ match [ 2 ] ] = match [ 1 ] ;
310+ }
311+ }
312+
313+ return refs ;
314+ }
315+
316+ private findDefaultBranch ( refs : Record < string , string > ) : string | null {
317+ // Check for HEAD reference
318+ if ( refs [ 'HEAD' ] ) {
319+ // HEAD might be a symbolic ref
320+ for ( const [ ref , sha ] of Object . entries ( refs ) ) {
321+ if ( ref . startsWith ( 'refs/heads/' ) && sha === refs [ 'HEAD' ] ) {
322+ return ref . replace ( 'refs/heads/' , '' ) ;
323+ }
324+ }
325+ }
326+
327+ // Fall back to common default branch names
328+ const commonDefaults = [ 'main' , 'master' , 'develop' , 'trunk' ] ;
329+ for ( const branch of commonDefaults ) {
330+ if ( refs [ `refs/heads/${ branch } ` ] ) {
331+ return branch ;
332+ }
333+ }
334+
335+ // Return the first branch found
336+ for ( const ref of Object . keys ( refs ) ) {
337+ if ( ref . startsWith ( 'refs/heads/' ) ) {
338+ return ref . replace ( 'refs/heads/' , '' ) ;
339+ }
340+ }
341+
342+ return null ;
343+ }
344+ }
345+
346+ // Register the custom Git adapter
347+ ProviderAdapterFactory . register ( 'custom' , CustomGitAdapter ) ;
0 commit comments