diff --git a/src/cargo/ops/cargo_package/vcs.rs b/src/cargo/ops/cargo_package/vcs.rs index 20dbedc7cb43..b961266e1bbc 100644 --- a/src/cargo/ops/cargo_package/vcs.rs +++ b/src/cargo/ops/cargo_package/vcs.rs @@ -1,5 +1,6 @@ //! Helpers to gather the VCS information for `cargo package`. +use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; @@ -133,12 +134,14 @@ fn git( // Find the intersection of dirty in git, and the src_files that would // be packaged. This is a lazy n^2 check, but seems fine with // thousands of files. + let mut dirty_files_outside_pkg_root = dirty_symlinks(pkg, repo, src_files)?; + dirty_files_outside_pkg_root.extend(dirty_metadata_paths(pkg, repo)?); let cwd = gctx.cwd(); let mut dirty_src_files: Vec<_> = src_files .iter() .filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path))) .map(|p| p.as_path_buf()) - .chain(dirty_metadata_paths(pkg, repo)?.iter()) + .chain(dirty_files_outside_pkg_root.iter()) .map(|path| { pathdiff::diff_paths(path, cwd) .as_ref() @@ -206,6 +209,36 @@ fn dirty_metadata_paths(pkg: &Package, repo: &git2::Repository) -> CargoResult CargoResult> { + let workdir = repo.workdir().unwrap(); + let mut dirty_symlinks = HashSet::new(); + for rel_path in src_files + .iter() + .filter(|p| p.is_symlink()) + .map(|p| p.as_path()) + // If inside package root. Don't bother checking git status. + .filter(|p| paths::strip_prefix_canonical(*p, pkg.root()).is_err()) + // Handle files outside package root but under git workdir, + .filter_map(|p| paths::strip_prefix_canonical(p, workdir).ok()) + { + // TODO: Should we warn users if there are like thousands of symlinks, + // which may hurt the performance? + if repo.status_file(&rel_path)? != git2::Status::CURRENT { + dirty_symlinks.insert(workdir.join(rel_path)); + } + } + Ok(dirty_symlinks) +} + /// Helper to collect dirty statuses for a single repo. fn collect_statuses(repo: &git2::Repository, dirty_files: &mut Vec) -> CargoResult<()> { let mut status_opts = git2::StatusOptions::new(); diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index b418513eace6..22639e237c29 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -1375,10 +1375,11 @@ fn dirty_file_outside_pkg_root_considered_dirty() { p.cargo("package --workspace --no-verify") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] 2 files in the working directory contain changes that were not yet committed into git: +[ERROR] 3 files in the working directory contain changes that were not yet committed into git: LICENSE README.md +lib.rs to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag