(main_config: &Path, managed_config: &Path)
| 1479 | } |
| 1480 | |
| 1481 | fn ensure_openshell_include(main_config: &Path, managed_config: &Path) -> Result<()> { |
| 1482 | if let Some(parent) = main_config.parent() { |
| 1483 | fs::create_dir_all(parent) |
| 1484 | .into_diagnostic() |
| 1485 | .wrap_err("failed to create ~/.ssh directory")?; |
| 1486 | } |
| 1487 | |
| 1488 | let include_line = render_include_line(managed_config); |
| 1489 | let contents = fs::read_to_string(main_config).unwrap_or_default(); |
| 1490 | let mut lines: Vec<&str> = contents.lines().collect(); |
| 1491 | lines.retain(|line| !ssh_config_includes_path(line, managed_config)); |
| 1492 | |
| 1493 | let insert_at = lines |
| 1494 | .iter() |
| 1495 | .position(|line| { |
| 1496 | let trimmed = line.trim_start(); |
| 1497 | trimmed.starts_with("Host ") || trimmed.starts_with("Match ") |
| 1498 | }) |
| 1499 | .unwrap_or(lines.len()); |
| 1500 | |
| 1501 | let mut out = Vec::new(); |
| 1502 | out.extend_from_slice(&lines[..insert_at]); |
| 1503 | if !out.is_empty() && !out.last().is_some_and(|line| line.is_empty()) { |
| 1504 | out.push(""); |
| 1505 | } |
| 1506 | out.push(&include_line); |
| 1507 | if insert_at < lines.len() && !lines[insert_at].is_empty() { |
| 1508 | out.push(""); |
| 1509 | } |
| 1510 | out.extend_from_slice(&lines[insert_at..]); |
| 1511 | |
| 1512 | let mut rendered = out.join("\n"); |
| 1513 | if !rendered.is_empty() { |
| 1514 | rendered.push('\n'); |
| 1515 | } |
| 1516 | |
| 1517 | fs::write(main_config, rendered) |
| 1518 | .into_diagnostic() |
| 1519 | .wrap_err("failed to update ~/.ssh/config")?; |
| 1520 | Ok(()) |
| 1521 | } |
| 1522 | |
| 1523 | fn host_line_matches(line: &str, alias: &str) -> bool { |
| 1524 | let trimmed = line.trim_start(); |
no test coverage detected