1
- use std:: { path:: Path , sync:: atomic:: AtomicBool , thread:: scope, time:: Instant } ;
1
+ use std:: {
2
+ cell:: RefCell ,
3
+ path:: Path ,
4
+ sync:: { atomic:: AtomicBool , Arc } ,
5
+ thread:: scope,
6
+ time:: Instant ,
7
+ } ;
2
8
3
9
use gix:: {
4
10
bstr:: { BString , ByteSlice } ,
5
- config:: tree:: User ,
6
- prelude:: FindExt ,
11
+ config:: tree:: {
12
+ gitoxide:: { self , Credentials } ,
13
+ Key , User ,
14
+ } ,
15
+ objs:: Data ,
16
+ odb:: { store:: Handle , Cache , Store } ,
17
+ oid,
7
18
progress:: Discard ,
8
19
refs:: {
9
20
transaction:: { Change , LogChange , RefEdit } ,
@@ -19,17 +30,12 @@ use crate::{errors::GitOpsError, opts::CliOptions, utils::Watchdog};
19
30
#[ derive( Clone , Deserialize ) ]
20
31
pub struct GitConfig {
21
32
#[ serde( deserialize_with = "url_from_string" ) ]
22
- url : Url ,
33
+ pub url : Arc < Box < dyn UrlProvider > > ,
23
34
#[ serde( default = "GitConfig::default_branch" ) ]
24
35
branch : String ,
25
36
}
26
37
27
38
impl GitConfig {
28
- pub fn safe_url ( & self ) -> String {
29
- // TODO Change to whitelist of allowed characters
30
- self . url . to_bstring ( ) . to_string ( ) . replace ( [ '/' , ':' ] , "_" )
31
- }
32
-
33
39
pub fn default_branch ( ) -> String {
34
40
"main" . to_owned ( )
35
41
}
@@ -41,18 +47,45 @@ impl TryFrom<&CliOptions> for GitConfig {
41
47
fn try_from ( opts : & CliOptions ) -> Result < Self , Self :: Error > {
42
48
let url = Url :: try_from ( opts. url . clone ( ) . unwrap ( ) ) . map_err ( GitOpsError :: InvalidUrl ) ?;
43
49
Ok ( GitConfig {
44
- url,
50
+ url : Arc :: new ( Box :: new ( DefaultUrlProvider { url } ) ) ,
45
51
branch : opts. branch . clone ( ) ,
46
52
} )
47
53
}
48
54
}
49
55
50
- fn url_from_string < ' de , D > ( deserializer : D ) -> Result < Url , D :: Error >
56
+ fn url_from_string < ' de , D > ( deserializer : D ) -> Result < Arc < Box < dyn UrlProvider > > , D :: Error >
51
57
where
52
58
D : Deserializer < ' de > ,
53
59
{
54
60
let s: String = Deserialize :: deserialize ( deserializer) ?;
55
- Url :: try_from ( s) . map_err ( serde:: de:: Error :: custom)
61
+ Ok ( Arc :: new ( Box :: new ( DefaultUrlProvider {
62
+ url : Url :: try_from ( s) . map_err ( serde:: de:: Error :: custom) ?,
63
+ } ) ) )
64
+ }
65
+
66
+ pub trait UrlProvider : Send + Sync {
67
+ fn url ( & self ) -> & Url ;
68
+ fn auth_url ( & self ) -> Result < Url , GitOpsError > ;
69
+
70
+ fn safe_url ( & self ) -> String {
71
+ // TODO Change to whitelist of allowed characters
72
+ self . url ( ) . to_bstring ( ) . to_string ( ) . replace ( [ '/' , ':' ] , "_" )
73
+ }
74
+ }
75
+
76
+ #[ derive( Clone ) ]
77
+ pub struct DefaultUrlProvider {
78
+ url : Url ,
79
+ }
80
+
81
+ impl UrlProvider for DefaultUrlProvider {
82
+ fn url ( & self ) -> & Url {
83
+ & self . url
84
+ }
85
+
86
+ fn auth_url ( & self ) -> Result < Url , GitOpsError > {
87
+ Ok ( self . url . clone ( ) )
88
+ }
56
89
}
57
90
58
91
fn clone_repo (
@@ -63,13 +96,18 @@ fn clone_repo(
63
96
let watchdog = Watchdog :: new ( deadline) ;
64
97
scope ( |s| {
65
98
s. spawn ( watchdog. runner ( ) ) ;
66
- let repo = gix:: prepare_clone ( config. url . clone ( ) , target)
67
- . unwrap ( )
68
- . fetch_only ( Discard , & watchdog)
69
- . map ( |( r, _) | r)
70
- . map_err ( GitOpsError :: InitRepo ) ;
99
+ let maybe_repo = config. url . auth_url ( ) . and_then ( |url| {
100
+ gix:: prepare_clone ( url, target)
101
+ . unwrap ( )
102
+ . with_in_memory_config_overrides ( vec ! [ gitoxide:: Credentials :: TERMINAL_PROMPT
103
+ . validated_assignment_fmt( & false )
104
+ . unwrap( ) ] )
105
+ . fetch_only ( Discard , & watchdog)
106
+ . map ( |( r, _) | r)
107
+ . map_err ( GitOpsError :: InitRepo )
108
+ } ) ;
71
109
watchdog. cancel ( ) ;
72
- repo
110
+ maybe_repo
73
111
} )
74
112
}
75
113
@@ -78,7 +116,7 @@ fn perform_fetch(
78
116
config : & GitConfig ,
79
117
cancel : & AtomicBool ,
80
118
) -> Result < Outcome , Box < dyn std:: error:: Error + Send + Sync > > {
81
- repo. remote_at ( config. url . clone ( ) )
119
+ repo. remote_at ( config. url . auth_url ( ) ? )
82
120
. unwrap ( )
83
121
. with_refspecs ( [ BString :: from ( config. branch . clone ( ) ) ] , Direction :: Fetch )
84
122
. unwrap ( )
@@ -122,6 +160,41 @@ fn fetch_repo(repo: &Repository, config: &GitConfig, deadline: Instant) -> Resul
122
160
Ok ( ( ) )
123
161
}
124
162
163
+ #[ derive( Clone ) ]
164
+ struct MaybeFind < Allow : Clone , Find : Clone > {
165
+ allow : std:: cell:: RefCell < Allow > ,
166
+ objects : Find ,
167
+ }
168
+
169
+ impl < Allow , Find > gix:: prelude:: Find for MaybeFind < Allow , Find >
170
+ where
171
+ Allow : FnMut ( & oid ) -> bool + Send + Clone ,
172
+ Find : gix:: prelude:: Find + Send + Clone ,
173
+ {
174
+ fn try_find < ' a > (
175
+ & self ,
176
+ id : & oid ,
177
+ buf : & ' a mut Vec < u8 > ,
178
+ ) -> Result < Option < Data < ' a > > , Box < dyn std:: error:: Error + Send + Sync > > {
179
+ if ( self . allow . borrow_mut ( ) ) ( id) {
180
+ self . objects . try_find ( id, buf)
181
+ } else {
182
+ Ok ( None )
183
+ }
184
+ }
185
+ }
186
+
187
+ fn can_we_please_have_impl_in_type_alias_already ( ) -> impl FnMut ( & oid ) -> bool + Send + Clone {
188
+ |_| true
189
+ }
190
+
191
+ fn make_finder ( odb : Cache < Handle < Arc < Store > > > ) -> impl gix:: prelude:: Find + Send + Clone {
192
+ MaybeFind {
193
+ allow : RefCell :: new ( can_we_please_have_impl_in_type_alias_already ( ) ) ,
194
+ objects : odb,
195
+ }
196
+ }
197
+
125
198
fn checkout_worktree (
126
199
repo : & Repository ,
127
200
branch : & str ,
@@ -142,10 +215,11 @@ fn checkout_worktree(
142
215
. unwrap ( ) ;
143
216
let ( mut state, _) = repo. index_from_tree ( & tree_id) . unwrap ( ) . into_parts ( ) ;
144
217
let odb = repo. objects . clone ( ) . into_arc ( ) . unwrap ( ) ;
218
+ let db = make_finder ( odb) ;
145
219
let _outcome = gix:: worktree:: state:: checkout (
146
220
& mut state,
147
221
workdir,
148
- move |oid , buf| odb . find_blob ( oid , buf ) ,
222
+ db ,
149
223
& Discard ,
150
224
& Discard ,
151
225
& AtomicBool :: default ( ) ,
@@ -173,6 +247,9 @@ where
173
247
let mut gitconfig = repo. config_snapshot_mut ( ) ;
174
248
gitconfig. set_value ( & User :: NAME , "kitops" ) . unwrap ( ) ;
175
249
gitconfig. set_value ( & User :: EMAIL , "none" ) . unwrap ( ) ;
250
+ gitconfig
251
+ . set_value ( & Credentials :: TERMINAL_PROMPT , "false" )
252
+ . unwrap ( ) ;
176
253
gitconfig. commit ( ) . unwrap ( ) ;
177
254
fetch_repo ( & repo, config, deadline) ?;
178
255
repo
@@ -181,3 +258,41 @@ where
181
258
} ;
182
259
checkout_worktree ( & repo, & config. branch , workdir)
183
260
}
261
+
262
+ #[ cfg( test) ]
263
+ mod tests {
264
+ use std:: {
265
+ sync:: Arc ,
266
+ time:: { Duration , Instant } ,
267
+ } ;
268
+
269
+ use crate :: {
270
+ errors:: GitOpsError ,
271
+ git:: { clone_repo, fetch_repo, GitConfig } ,
272
+ testutils:: TestUrl ,
273
+ } ;
274
+
275
+ #[ test]
276
+ fn clone_with_bad_url ( ) {
277
+ let config = GitConfig {
278
+ url : Arc :: new ( Box :: new ( TestUrl :: new ( Some ( GitOpsError :: TestError ) ) ) ) ,
279
+ branch : "main" . into ( ) ,
280
+ } ;
281
+ let deadline = Instant :: now ( ) + Duration :: from_secs ( 61 ) ; // Fail tests that time out
282
+ let target = tempfile:: tempdir ( ) . unwrap ( ) ;
283
+ let result = clone_repo ( & config, deadline, target. path ( ) ) ;
284
+ assert ! ( matches!( result, Err ( GitOpsError :: TestError ) ) ) ;
285
+ }
286
+
287
+ #[ test]
288
+ fn fetch_with_bad_url ( ) {
289
+ let repo = gix:: open ( "." ) . unwrap ( ) ;
290
+ let config = GitConfig {
291
+ url : Arc :: new ( Box :: new ( TestUrl :: new ( Some ( GitOpsError :: TestError ) ) ) ) ,
292
+ branch : "main" . into ( ) ,
293
+ } ;
294
+ let deadline = Instant :: now ( ) + Duration :: from_secs ( 61 ) ; // Fail tests that time out
295
+ let result = fetch_repo ( & repo, & config, deadline) ;
296
+ assert ! ( result. is_err( ) ) ;
297
+ }
298
+ }
0 commit comments