Skip to content

Commit f5316ca

Browse files
jamesadevineCopilot
andcommitted
fix: YAML path matching and legacy SSH URL support
- Strip leading '/' from ADO yamlFilename before comparing to local relative paths (ADO stores e.g. '/.azdo/pipelines/agent.yml') - Add legacy SSH URL format: git@vs-ssh.visualstudio.com:v3/... - Fix step numbering in run() comments (3→4, was skipping 4) - Add tests for YAML path matching with/without leading slash - Add test for legacy SSH URL parsing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2e2c4fd commit f5316ca

1 file changed

Lines changed: 68 additions & 4 deletions

File tree

src/configure.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ pub fn parse_ado_remote(remote_url: &str) -> Result<AdoContext> {
3636
let url = remote_url.trim();
3737

3838
// SSH format: git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
39-
if let Some(rest) = url.strip_prefix("git@ssh.dev.azure.com:v3/") {
39+
// Also handles legacy: git@vs-ssh.visualstudio.com:v3/{org}/{project}/{repo}
40+
if let Some(rest) = url
41+
.strip_prefix("git@ssh.dev.azure.com:v3/")
42+
.or_else(|| url.strip_prefix("git@vs-ssh.visualstudio.com:v3/"))
43+
{
4044
let parts: Vec<&str> = rest.splitn(3, '/').collect();
4145
if parts.len() >= 3 {
4246
let repo_name = parts[2].trim_end_matches(".git");
@@ -290,12 +294,17 @@ async fn match_definitions(
290294
let yaml_path_str = pipeline.yaml_path.to_string_lossy();
291295
let yaml_path_normalized = yaml_path_str.replace('\\', "/");
292296

293-
// Strategy 1: Match by YAML filename in the definition
297+
// Strategy 1: Match by YAML filename in the definition.
298+
// ADO stores yamlFilename with a leading '/' (e.g., "/.azdo/pipelines/agent.yml"),
299+
// so we strip it before comparing to the locally-detected relative path.
294300
let path_match = definitions.iter().find(|d| {
295301
d.process
296302
.as_ref()
297303
.and_then(|p| p.yaml_filename.as_ref())
298-
.is_some_and(|f| f.replace('\\', "/") == yaml_path_normalized)
304+
.is_some_and(|f| {
305+
let f_normalized = f.trim_start_matches('/').replace('\\', "/");
306+
f_normalized == yaml_path_normalized
307+
})
299308
});
300309

301310
if let Some(def) = path_match {
@@ -566,7 +575,7 @@ pub async fn run(
566575
}
567576
println!();
568577

569-
// Step 5: Update GITHUB_TOKEN
578+
// Step 4: Update GITHUB_TOKEN
570579
if dry_run {
571580
println!("Dry run \u{2014} no changes applied.");
572581
println!(
@@ -650,6 +659,15 @@ mod tests {
650659
assert_eq!(ctx.repo_name, "myrepo");
651660
}
652661

662+
#[test]
663+
fn test_parse_ado_remote_legacy_ssh() {
664+
let url = "git@vs-ssh.visualstudio.com:v3/myorg/myproject/myrepo";
665+
let ctx = parse_ado_remote(url).unwrap();
666+
assert_eq!(ctx.org_url, "https://dev.azure.com/myorg");
667+
assert_eq!(ctx.project, "myproject");
668+
assert_eq!(ctx.repo_name, "myrepo");
669+
}
670+
653671
#[test]
654672
fn test_parse_ado_remote_invalid() {
655673
assert!(parse_ado_remote("https://github.com/user/repo").is_err());
@@ -666,6 +684,52 @@ mod tests {
666684
}
667685
}
668686

687+
fn make_def_with_yaml(id: u64, name: &str, yaml_filename: &str) -> DefinitionSummary {
688+
DefinitionSummary {
689+
id,
690+
name: name.to_string(),
691+
process: Some(ProcessInfo {
692+
yaml_filename: Some(yaml_filename.to_string()),
693+
}),
694+
}
695+
}
696+
697+
// ==================== YAML path matching ====================
698+
699+
#[test]
700+
fn test_yaml_path_match_strips_leading_slash() {
701+
// 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);
714+
}
715+
716+
#[test]
717+
fn test_yaml_path_match_without_leading_slash() {
718+
// 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);
731+
}
732+
669733
#[test]
670734
fn test_fuzzy_match_single_unambiguous() {
671735
let defs = vec![

0 commit comments

Comments
 (0)