diff --git a/src/cargo/sources/path.rs b/src/cargo/sources/path.rs index 6296f088b1d..120a799f5b0 100644 --- a/src/cargo/sources/path.rs +++ b/src/cargo/sources/path.rs @@ -991,6 +991,10 @@ fn read_packages( source_id: SourceId, gctx: &GlobalContext, ) -> CargoResult>> { + // Normalize for consistency. + // See https://github.com/rust-lang/cargo/issues/15981 + // Otherwise, a `CARGO_HOME` with `..` can collect a package twice and incorrectly warn about a duplicate. + let path = &paths::normalize_path(path); let mut all_packages = HashMap::default(); let mut visited = HashSet::::default(); let mut errors = Vec::::new(); diff --git a/tests/testsuite/git.rs b/tests/testsuite/git.rs index 1d1b72b8c66..3c4771eeada 100644 --- a/tests/testsuite/git.rs +++ b/tests/testsuite/git.rs @@ -1250,6 +1250,76 @@ in favor of [ROOT]/home/.cargo/git/checkouts/dep-[HASH]/[..]/duplicate1/Cargo.to .run(); } +#[cargo_test] +fn no_duplicate_package_warning_with_dotdot_cargo_home() { + // Regression test for https://github.com/rust-lang/cargo/issues/15981. + // + // A `CARGO_HOME` containing a `..` segment can make the git checkout path retain + // the `..`, so a workspace member discovered by walking the checkout (raw path) + // and by following the root's `path` dependency (normalized path) looked + // like two different packages, producing an incorrect duplicate warning. + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.1.0" + edition = "2015" + + [dependencies] + member = { path = "member" } + "#, + ) + .file("src/lib.rs", "") + .file( + "member/Cargo.toml", + r#" + [package] + name = "member" + version = "0.1.0" + edition = "2015" + "#, + ) + .file("member/src/lib.rs", "") + }); + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2015" + + [dependencies] + dep = {{ git = '{}' }} + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + // `paths::home()` is created by the test harness, so the `..` resolves on disk. + let cargo_home = paths::home().join("..").join("cargo-home"); + + p.cargo("check") + .env("CARGO_HOME", &cargo_home) + .with_stderr_data(str![[r#" +[UPDATING] git repository `[ROOTURL]/dep` +[LOCKING] 2 packages to latest compatible versions +[CHECKING] member v0.1.0 ([ROOTURL]/dep#[..]) +[CHECKING] dep v0.1.0 ([ROOTURL]/dep#[..]) +[CHECKING] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + #[cargo_test] fn unused_ambiguous_published_deps() { let project = project();