Skip to content

Commit 4a61d1c

Browse files
committed
Auto merge of #8186 - ehuss:fix-git-strip-prefix, r=alexcrichton
Fix error with git repo discovery and symlinks. There are some cases where Cargo would generate an error when attempting to discover if a package is inside a git repo when the git repo has a symlink somewhere in its ancestor paths. One way this manifests is with `cargo install --git ...` where the given repo has a `build.rs` script. Another scenario is `cargo build --manifest-path somelink/Cargo.toml` where `somelink` is a symlink to the real thing. The issue is that libgit2 is normalizing paths and removing symlinks, but the path Cargo uses is the path with symlinks. This was introduced in #8095. The solution is to try to canonicalize both paths when trying to get a repo-relative path. If that fails for whatever reason, it shouldn't generate an error since this is just a "best effort" attempt to use git to list package files. Fixes #8183
2 parents ba832ac + 5bd74c4 commit 4a61d1c

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

src/cargo/sources/path.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,18 @@ impl<'cfg> PathSource<'cfg> {
197197
repo.path().display()
198198
)
199199
})?;
200-
let repo_relative_path = root.strip_prefix(repo_root).chain_err(|| {
201-
format!(
202-
"expected git repo {} to be parent of package {}",
203-
repo.path().display(),
204-
root.display()
205-
)
206-
})?;
200+
let repo_relative_path = match paths::strip_prefix_canonical(root, repo_root) {
201+
Ok(p) => p,
202+
Err(e) => {
203+
log::warn!(
204+
"cannot determine if path `{:?}` is in git repo `{:?}`: {:?}",
205+
root,
206+
repo_root,
207+
e
208+
);
209+
return Ok(None);
210+
}
211+
};
207212
let manifest_path = repo_relative_path.join("Cargo.toml");
208213
if index.get_path(&manifest_path, 0).is_some() {
209214
return Ok(Some(self.list_files_git(pkg, &repo, filter)?));

src/cargo/util/paths.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,25 @@ pub fn set_file_time_no_err<P: AsRef<Path>>(path: P, time: FileTime) {
411411
),
412412
}
413413
}
414+
415+
/// Strips `base` from `path`.
416+
///
417+
/// This canonicalizes both paths before stripping. This is useful if the
418+
/// paths are obtained in different ways, and one or the other may or may not
419+
/// have been normalized in some way.
420+
pub fn strip_prefix_canonical<P: AsRef<Path>>(
421+
path: P,
422+
base: P,
423+
) -> Result<PathBuf, std::path::StripPrefixError> {
424+
// Not all filesystems support canonicalize. Just ignore if it doesn't work.
425+
let safe_canonicalize = |path: &Path| match path.canonicalize() {
426+
Ok(p) => p,
427+
Err(e) => {
428+
log::warn!("cannot canonicalize {:?}: {:?}", path, e);
429+
path.to_path_buf()
430+
}
431+
};
432+
let canon_path = safe_canonicalize(path.as_ref());
433+
let canon_base = safe_canonicalize(base.as_ref());
434+
canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
435+
}

tests/testsuite/install.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use cargo_test_support::install::{
1010
};
1111
use cargo_test_support::paths;
1212
use cargo_test_support::registry::Package;
13-
use cargo_test_support::{basic_manifest, cargo_process, project, NO_SUCH_FILE_ERR_MSG};
13+
use cargo_test_support::{
14+
basic_manifest, cargo_process, project, symlink_supported, t, NO_SUCH_FILE_ERR_MSG,
15+
};
1416

1517
fn pkg(name: &str, vers: &str) {
1618
Package::new(name, vers)
@@ -1458,3 +1460,40 @@ fn git_install_reads_workspace_manifest() {
14581460
.with_stderr_contains(" invalid type: integer `3`[..]")
14591461
.run();
14601462
}
1463+
1464+
#[cargo_test]
1465+
fn install_git_with_symlink_home() {
1466+
// Ensure that `cargo install` with a git repo is OK when CARGO_HOME is a
1467+
// symlink, and uses an build script.
1468+
if !symlink_supported() {
1469+
return;
1470+
}
1471+
let p = git::new("foo", |p| {
1472+
p.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
1473+
.file("src/main.rs", "fn main() {}")
1474+
// This triggers discover_git_and_list_files for detecting changed files.
1475+
.file("build.rs", "fn main() {}")
1476+
});
1477+
#[cfg(unix)]
1478+
use std::os::unix::fs::symlink;
1479+
#[cfg(windows)]
1480+
use std::os::windows::fs::symlink_dir as symlink;
1481+
1482+
let actual = paths::root().join("actual-home");
1483+
t!(std::fs::create_dir(&actual));
1484+
t!(symlink(&actual, paths::home().join(".cargo")));
1485+
cargo_process("install --git")
1486+
.arg(p.url().to_string())
1487+
.with_stderr(
1488+
"\
1489+
[UPDATING] git repository [..]
1490+
[INSTALLING] foo v1.0.0 [..]
1491+
[COMPILING] foo v1.0.0 [..]
1492+
[FINISHED] [..]
1493+
[INSTALLING] [..]home/.cargo/bin/foo[..]
1494+
[INSTALLED] package `foo [..]
1495+
[WARNING] be sure to add [..]
1496+
",
1497+
)
1498+
.run();
1499+
}

0 commit comments

Comments
 (0)