@@ -127,3 +127,117 @@ mod interpolate {
127127 std:: env:: current_dir ( ) . unwrap ( ) . join ( name) . into ( )
128128 }
129129}
130+
131+ mod optional_prefix {
132+ use std:: borrow:: Cow ;
133+
134+ use bstr:: ByteSlice ;
135+ use crate :: { b, cow_str} ;
136+
137+ #[ test]
138+ fn path_without_optional_prefix_is_not_optional ( ) {
139+ let path = gix_config_value:: Path :: from ( Cow :: Borrowed ( b ( "/some/path" ) ) ) ;
140+ assert ! ( !path. is_optional( ) , "path without prefix should not be optional" ) ;
141+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" ) ;
142+ }
143+
144+ #[ test]
145+ fn path_with_optional_prefix_is_optional ( ) {
146+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)/some/path" ) ) ;
147+ assert ! ( path. is_optional( ) , "path with :(optional) prefix should be optional" ) ;
148+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" , "prefix should be stripped" ) ;
149+ }
150+
151+ #[ test]
152+ fn optional_prefix_with_relative_path ( ) {
153+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)relative/path" ) ) ;
154+ assert ! ( path. is_optional( ) ) ;
155+ assert_eq ! ( path. value. as_ref( ) , b"relative/path" ) ;
156+ }
157+
158+ #[ test]
159+ fn optional_prefix_with_tilde_expansion ( ) {
160+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)~/config/file" ) ) ;
161+ assert ! ( path. is_optional( ) ) ;
162+ assert_eq ! ( path. value. as_ref( ) , b"~/config/file" , "tilde should be preserved for interpolation" ) ;
163+ }
164+
165+ #[ test]
166+ fn optional_prefix_with_prefix_substitution ( ) {
167+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)%(prefix)/share/git" ) ) ;
168+ assert ! ( path. is_optional( ) ) ;
169+ assert_eq ! ( path. value. as_ref( ) , b"%(prefix)/share/git" , "prefix should be preserved for interpolation" ) ;
170+ }
171+
172+ #[ test]
173+ fn optional_prefix_with_windows_path ( ) {
174+ let path = gix_config_value:: Path :: from ( cow_str ( r":(optional)C:\Users\file" ) ) ;
175+ assert ! ( path. is_optional( ) ) ;
176+ assert_eq ! ( path. value. as_ref( ) , br"C:\Users\file" ) ;
177+ }
178+
179+ #[ test]
180+ fn optional_prefix_followed_by_empty_path ( ) {
181+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)" ) ) ;
182+ assert ! ( path. is_optional( ) ) ;
183+ assert_eq ! ( path. value. as_ref( ) , b"" , "empty path after prefix is valid" ) ;
184+ }
185+
186+ #[ test]
187+ fn partial_optional_string_is_not_treated_as_prefix ( ) {
188+ let path = gix_config_value:: Path :: from ( cow_str ( ":(opt)ional/path" ) ) ;
189+ assert ! ( !path. is_optional( ) , "incomplete prefix should not be treated as optional marker" ) ;
190+ assert_eq ! ( path. value. as_ref( ) , b":(opt)ional/path" ) ;
191+ }
192+
193+ #[ test]
194+ fn optional_prefix_case_sensitive ( ) {
195+ let path = gix_config_value:: Path :: from ( cow_str ( ":(OPTIONAL)/some/path" ) ) ;
196+ assert ! ( !path. is_optional( ) , "prefix should be case-sensitive" ) ;
197+ assert_eq ! ( path. value. as_ref( ) , b":(OPTIONAL)/some/path" ) ;
198+ }
199+
200+ #[ test]
201+ fn optional_prefix_with_spaces ( ) {
202+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional) /path/with/space" ) ) ;
203+ assert ! ( path. is_optional( ) ) ;
204+ assert_eq ! ( path. value. as_ref( ) , b" /path/with/space" , "space after prefix should be preserved" ) ;
205+ }
206+
207+ #[ test]
208+ fn interpolate_preserves_optional_flag ( ) -> crate :: Result {
209+ use gix_config_value:: path;
210+
211+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)/absolute/path" ) ) ;
212+ assert ! ( path. is_optional( ) ) ;
213+
214+ let interpolated = path. interpolate ( path:: interpolate:: Context :: default ( ) ) ?;
215+ assert_eq ! ( interpolated. as_ref( ) , std:: path:: Path :: new( "/absolute/path" ) ) ;
216+
217+ Ok ( ( ) )
218+ }
219+
220+ #[ test]
221+ fn borrowed_path_stays_borrowed_after_prefix_stripping ( ) {
222+ // Verify that we don't unnecessarily allocate when stripping the prefix from borrowed data
223+ let borrowed_input: & [ u8 ] = b":(optional)/some/path" ;
224+ let path = gix_config_value:: Path :: from ( Cow :: Borrowed ( borrowed_input. as_bstr ( ) ) ) ;
225+
226+ assert ! ( path. is_optional( ) ) ;
227+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" ) ;
228+ // Verify it's still borrowed (no unnecessary allocation)
229+ assert ! ( matches!( path. value, Cow :: Borrowed ( _) ) ) ;
230+ }
231+
232+ #[ test]
233+ fn owned_path_stays_owned_after_prefix_stripping ( ) {
234+ // Verify that owned data remains owned after prefix stripping (but efficiently)
235+ let owned_input = bstr:: BString :: from ( ":(optional)/some/path" ) ;
236+ let path = gix_config_value:: Path :: from ( Cow :: Owned ( owned_input) ) ;
237+
238+ assert ! ( path. is_optional( ) ) ;
239+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" ) ;
240+ // Verify it's still owned
241+ assert ! ( matches!( path. value, Cow :: Owned ( _) ) ) ;
242+ }
243+ }
0 commit comments