@@ -127,3 +127,119 @@ 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 crate :: { b, cow_str} ;
135+ use bstr:: ByteSlice ;
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 ! (
163+ path. value. as_ref( ) ,
164+ b"~/config/file" ,
165+ "tilde should be preserved for interpolation"
166+ ) ;
167+ }
168+
169+ #[ test]
170+ fn optional_prefix_with_prefix_substitution ( ) {
171+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)%(prefix)/share/git" ) ) ;
172+ assert ! ( path. is_optional) ;
173+ assert_eq ! (
174+ path. value. as_ref( ) ,
175+ b"%(prefix)/share/git" ,
176+ "prefix should be preserved for interpolation"
177+ ) ;
178+ }
179+
180+ #[ test]
181+ fn optional_prefix_with_windows_path ( ) {
182+ let path = gix_config_value:: Path :: from ( cow_str ( r":(optional)C:\Users\file" ) ) ;
183+ assert ! ( path. is_optional) ;
184+ assert_eq ! ( path. value. as_ref( ) , br"C:\Users\file" ) ;
185+ }
186+
187+ #[ test]
188+ fn optional_prefix_followed_by_empty_path ( ) {
189+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional)" ) ) ;
190+ assert ! ( path. is_optional) ;
191+ assert_eq ! ( path. value. as_ref( ) , b"" , "empty path after prefix is valid" ) ;
192+ }
193+
194+ #[ test]
195+ fn partial_optional_string_is_not_treated_as_prefix ( ) {
196+ let path = gix_config_value:: Path :: from ( cow_str ( ":(opt)ional/path" ) ) ;
197+ assert ! (
198+ !path. is_optional,
199+ "incomplete prefix should not be treated as optional marker"
200+ ) ;
201+ assert_eq ! ( path. value. as_ref( ) , b":(opt)ional/path" ) ;
202+ }
203+
204+ #[ test]
205+ fn optional_prefix_case_sensitive ( ) {
206+ let path = gix_config_value:: Path :: from ( cow_str ( ":(OPTIONAL)/some/path" ) ) ;
207+ assert ! ( !path. is_optional, "prefix should be case-sensitive" ) ;
208+ assert_eq ! ( path. value. as_ref( ) , b":(OPTIONAL)/some/path" ) ;
209+ }
210+
211+ #[ test]
212+ fn optional_prefix_with_spaces ( ) {
213+ let path = gix_config_value:: Path :: from ( cow_str ( ":(optional) /path/with/space" ) ) ;
214+ assert ! ( path. is_optional) ;
215+ assert_eq ! (
216+ path. value. as_ref( ) ,
217+ b" /path/with/space" ,
218+ "space after prefix should be preserved"
219+ ) ;
220+ }
221+
222+ #[ test]
223+ fn borrowed_path_stays_borrowed_after_prefix_stripping ( ) {
224+ // Verify that we don't unnecessarily allocate when stripping the prefix from borrowed data
225+ let borrowed_input: & [ u8 ] = b":(optional)/some/path" ;
226+ let path = gix_config_value:: Path :: from ( Cow :: Borrowed ( borrowed_input. as_bstr ( ) ) ) ;
227+
228+ assert ! ( path. is_optional) ;
229+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" ) ;
230+ // Verify it's still borrowed (no unnecessary allocation)
231+ assert ! ( matches!( path. value, Cow :: Borrowed ( _) ) ) ;
232+ }
233+
234+ #[ test]
235+ fn owned_path_stays_owned_after_prefix_stripping ( ) {
236+ // Verify that owned data remains owned after prefix stripping
237+ let owned_input = bstr:: BString :: from ( ":(optional)/some/path" ) ;
238+ let path = gix_config_value:: Path :: from ( Cow :: Owned ( owned_input) ) ;
239+
240+ assert ! ( path. is_optional) ;
241+ assert_eq ! ( path. value. as_ref( ) , b"/some/path" ) ;
242+ // Verify it's still owned
243+ assert ! ( matches!( path. value, Cow :: Owned ( _) ) ) ;
244+ }
245+ }
0 commit comments