@@ -32,6 +32,7 @@ pub struct AdoContext {
3232/// - HTTPS: `https://dev.azure.com/{org}/{project}/_git/{repo}`
3333/// - HTTPS (legacy): `https://{org}.visualstudio.com/{project}/_git/{repo}`
3434/// - SSH: `git@ssh.dev.azure.com:v3/{org}/{project}/{repo}`
35+ /// - SSH (legacy): `git@vs-ssh.visualstudio.com:v3/{org}/{project}/{repo}`
3536pub fn parse_ado_remote ( remote_url : & str ) -> Result < AdoContext > {
3637 let url = remote_url. trim ( ) ;
3738
@@ -269,6 +270,15 @@ fn fuzzy_match_by_name(agent_name: &str, definitions: &[DefinitionSummary]) -> F
269270 }
270271}
271272
273+ /// Normalize an ADO YAML filename for comparison with local paths.
274+ ///
275+ /// ADO's Build Definitions API stores `yamlFilename` with a leading `/`
276+ /// (e.g., `/.azdo/pipelines/agent.yml`) and may use backslashes on Windows.
277+ /// This strips the leading `/` and normalizes separators to forward slashes.
278+ fn normalize_ado_yaml_path ( path : & str ) -> String {
279+ path. replace ( '\\' , "/" ) . trim_start_matches ( '/' ) . to_string ( )
280+ }
281+
272282/// Match detected pipeline YAML files to ADO pipeline definitions.
273283///
274284/// Strategy:
@@ -301,10 +311,7 @@ async fn match_definitions(
301311 d. process
302312 . as_ref ( )
303313 . and_then ( |p| p. yaml_filename . as_ref ( ) )
304- . is_some_and ( |f| {
305- let f_normalized = f. trim_start_matches ( '/' ) . replace ( '\\' , "/" ) ;
306- f_normalized == yaml_path_normalized
307- } )
314+ . is_some_and ( |f| normalize_ado_yaml_path ( f) == yaml_path_normalized)
308315 } ) ;
309316
310317 if let Some ( def) = path_match {
@@ -699,35 +706,27 @@ mod tests {
699706 #[ test]
700707 fn test_yaml_path_match_strips_leading_slash ( ) {
701708 // ADO stores yamlFilename with a leading '/'
702- let def = make_def_with_yaml ( 1 , "My Pipeline" , "/.azdo/pipelines/agent.yml" ) ;
703- let local_path = ".azdo/pipelines/agent.yml" ;
704- let f_normalized = def
705- . process
706- . as_ref ( )
707- . unwrap ( )
708- . yaml_filename
709- . as_ref ( )
710- . unwrap ( )
711- . trim_start_matches ( '/' )
712- . replace ( '\\' , "/" ) ;
713- assert_eq ! ( f_normalized, local_path) ;
709+ assert_eq ! (
710+ normalize_ado_yaml_path( "/.azdo/pipelines/agent.yml" ) ,
711+ ".azdo/pipelines/agent.yml"
712+ ) ;
714713 }
715714
716715 #[ test]
717716 fn test_yaml_path_match_without_leading_slash ( ) {
718717 // Some ADO instances may store without leading '/'
719- let def = make_def_with_yaml ( 1 , "My Pipeline" , ".azdo/pipelines/agent.yml" ) ;
720- let local_path = ".azdo/pipelines/agent.yml" ;
721- let f_normalized = def
722- . process
723- . as_ref ( )
724- . unwrap ( )
725- . yaml_filename
726- . as_ref ( )
727- . unwrap ( )
728- . trim_start_matches ( '/' )
729- . replace ( '\\' , "/" ) ;
730- assert_eq ! ( f_normalized , local_path ) ;
718+ assert_eq ! (
719+ normalize_ado_yaml_path ( ".azdo/pipelines/agent.yml" ) ,
720+ ".azdo/pipelines/agent.yml"
721+ ) ;
722+ }
723+
724+ # [ test ]
725+ fn test_yaml_path_match_backslash_normalization ( ) {
726+ assert_eq ! (
727+ normalize_ado_yaml_path ( " \\ .azdo \\ pipelines \\ agent.yml" ) ,
728+ ".azdo/pipelines/agent.yml"
729+ ) ;
731730 }
732731
733732 #[ test]
0 commit comments