Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ skill = { version = "0.8", default-features = false, features = ["network"] }
agentflare-jobs = { path = "crates/agentflare-jobs" }
machine-uid = "0.6.0"
agentflare-workspace-hack = { version = "0.1", path = "agentflare-workspace-hack" }
dunce = "1"

[target.'cfg(unix)'.dependencies]
libc = "0.2"
Expand Down
10 changes: 10 additions & 0 deletions src/code/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ pub fn impact_for_path(repo_root: &Path, target: &Path) -> Result<ImpactReport,
} else {
repo_root.join(target)
};
// `cargo metadata` returns manifest paths canonicalized (symlink-free —
// notably resolving macOS's /var -> /private/var), but `absolute_target`
// is built from the caller-supplied root as-is. Canonicalize it the same
// way so a file that's genuinely in the workspace isn't rejected as
// outside it just because it was reached through a symlink. Use `dunce`
// rather than `std::fs::canonicalize` directly: on Windows, std adds a
// `\\?\` UNC prefix that cargo's own (unprefixed) manifest paths don't
// have, which would reintroduce the same mismatch on that platform.
// Falls back to the uncanonicalized path when the target doesn't exist.
let absolute_target = dunce::canonicalize(&absolute_target).unwrap_or(absolute_target);
let owner = graph
.resolve_owner_crate(&absolute_target)
.ok_or_else(|| ImpactError::NotInWorkspace(absolute_target.clone()))?;
Expand Down
10 changes: 10 additions & 0 deletions src/code/workspace_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ impl WorkspaceGraph {
.parent()
.unwrap_or_else(|| Path::new(&pkg.manifest_path))
.to_path_buf();
// `cargo metadata`'s manifest_path can come back in a different
// path representation than paths callers build themselves — a
// symlink-free real path on macOS, or (observed on GitHub
// Actions Windows runners) an 8.3 short-name component like
// `RUNNER~1` instead of `runneradmin`. Canonicalize here so
// `resolve_owner_crate`'s prefix check compares apples to
// apples against a caller path canonicalized the same way.
// Falls back to the raw path when it can't be resolved (e.g.
// the synthetic fixture paths used in unit tests below).
let manifest_dir = dunce::canonicalize(&manifest_dir).unwrap_or(manifest_dir);
let own_test_paths = pkg
.targets
.iter()
Expand Down
24 changes: 24 additions & 0 deletions tests/code_impact_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@ fn impact_of_a_file_outside_any_workspace_member_is_a_clear_error() {
));
}

// Simulates macOS's `/var` -> `/private/var`: the caller-supplied root is a
// symlink to the real directory. `cargo metadata` (invoked with that symlink
// as its cwd) reports canonical, symlink-free manifest paths, so the
// membership check must canonicalize the candidate file the same way, or a
// file that's genuinely inside the workspace gets rejected as outside it.
#[test]
#[cfg(unix)]
fn impact_resolves_a_target_reached_through_a_symlinked_workspace_root() {
let real = build_fixture_workspace();
let real_root = real.path();

let link_parent = tempfile::tempdir().unwrap();
let link_root = link_parent.path().join("link-to-workspace");
std::os::unix::fs::symlink(real_root, &link_root).unwrap();

let report = agentflare::code::impact_for_path(
&link_root,
&link_root.join("crates/jobs/src/supervisor.rs"),
)
.unwrap();

assert_eq!(report.owner_crate, "jobs");
}

#[test]
fn impact_with_no_cargo_toml_is_a_clear_error() {
let dir = tempfile::tempdir().unwrap();
Expand Down
Loading