Skip to content

Commit d73d2ca

Browse files
authored
fix(package): warn if symlinks checked out as plain text files (#14994)
### What does this PR try to resolve? `cargo package` will warn users when git `core.symlinks` is `false` and some symlinks were checked out as plain files during packaging. Git config [`core.symlinks`] defaults to true when unset. In git-for-windows (and git as well), the config should be set to false explicitly when the repo was created, if symlink support wasn't detected [^1]. We assume the config was always set at creation time and never changed. So, if it is true, we don't bother users with any warning. [^1]: https://github.com/git-for-windows/git/blob/f1241afcc7956918d5da33ef74abd9cbba369247/setup.c#L2394-L2403 [`core.symlinks`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks ### How should we test and review this PR? CI passes. This shares two commits 42dc4ef and c8c8223 with #14981. I didn't commit to fix all symlink issues all at once. This PR demonstrates how we could leverage metadata in `PathEntry`. Maybe later we can really follow plain-text symlinks and resolve the issue. ### Additional information cc #5664
2 parents 0499e31 + 059fe16 commit d73d2ca

File tree

6 files changed

+295
-18
lines changed

6 files changed

+295
-18
lines changed

src/cargo/ops/cargo_package/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::core::Workspace;
1616
use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
1717
use crate::ops::lockfile::LOCKFILE_NAME;
1818
use crate::ops::registry::{infer_registry, RegistryOrIndex};
19+
use crate::sources::path::PathEntry;
1920
use crate::sources::registry::index::{IndexPackage, RegistryDependency};
2021
use crate::sources::{PathSource, CRATES_IO_REGISTRY};
2122
use crate::util::cache_lock::CacheLockMode;
@@ -396,7 +397,7 @@ fn prepare_archive(
396397
fn build_ar_list(
397398
ws: &Workspace<'_>,
398399
pkg: &Package,
399-
src_files: Vec<PathBuf>,
400+
src_files: Vec<PathEntry>,
400401
vcs_info: Option<vcs::VcsInfo>,
401402
) -> CargoResult<Vec<ArchiveFile>> {
402403
let mut result = HashMap::new();
@@ -420,7 +421,7 @@ fn build_ar_list(
420421
.push(ArchiveFile {
421422
rel_path: rel_path.to_owned(),
422423
rel_str: rel_str.to_owned(),
423-
contents: FileContents::OnDisk(src_file.clone()),
424+
contents: FileContents::OnDisk(src_file.to_path_buf()),
424425
});
425426
}
426427
}

src/cargo/ops/cargo_package/vcs.rs

+52-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use serde::Serialize;
99
use tracing::debug;
1010

1111
use crate::core::Package;
12+
use crate::sources::PathEntry;
1213
use crate::CargoResult;
1314
use crate::GlobalContext;
1415

@@ -41,7 +42,7 @@ pub struct GitVcsInfo {
4142
#[tracing::instrument(skip_all)]
4243
pub fn check_repo_state(
4344
p: &Package,
44-
src_files: &[PathBuf],
45+
src_files: &[PathEntry],
4546
gctx: &GlobalContext,
4647
opts: &PackageOpts<'_>,
4748
) -> CargoResult<Option<VcsInfo>> {
@@ -91,6 +92,8 @@ pub fn check_repo_state(
9192
return Ok(None);
9293
}
9394

95+
warn_symlink_checked_out_as_plain_text_file(gctx, src_files, &repo)?;
96+
9497
debug!(
9598
"found (git) Cargo.toml at `{}` in workdir `{}`",
9699
path.display(),
@@ -110,11 +113,57 @@ pub fn check_repo_state(
110113
return Ok(Some(VcsInfo { git, path_in_vcs }));
111114
}
112115

116+
/// Warns if any symlinks were checked out as plain text files.
117+
///
118+
/// Git config [`core.symlinks`] defaults to true when unset.
119+
/// In git-for-windows (and git as well),
120+
/// the config should be set to false explicitly when the repo was created,
121+
/// if symlink support wasn't detected [^1].
122+
///
123+
/// We assume the config was always set at creation time and never changed.
124+
/// So, if it is true, we don't bother users with any warning.
125+
///
126+
/// [^1]: <https://github.com/git-for-windows/git/blob/f1241afcc7956918d5da33ef74abd9cbba369247/setup.c#L2394-L2403>
127+
///
128+
/// [`core.symlinks`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks
129+
fn warn_symlink_checked_out_as_plain_text_file(
130+
gctx: &GlobalContext,
131+
src_files: &[PathEntry],
132+
repo: &git2::Repository,
133+
) -> CargoResult<()> {
134+
if repo
135+
.config()
136+
.and_then(|c| c.get_bool("core.symlinks"))
137+
.unwrap_or(true)
138+
{
139+
return Ok(());
140+
}
141+
142+
if src_files.iter().any(|f| f.maybe_plain_text_symlink()) {
143+
let mut shell = gctx.shell();
144+
shell.warn(format_args!(
145+
"found symbolic links that may be checked out as regular files for git repo at `{}`\n\
146+
This might cause the `.crate` file to include incorrect or incomplete files",
147+
repo.workdir().unwrap().display(),
148+
))?;
149+
let extra_note = if cfg!(windows) {
150+
"\nAnd on Windows, enable the Developer Mode to support symlinks"
151+
} else {
152+
""
153+
};
154+
shell.note(format_args!(
155+
"to avoid this, set the Git config `core.symlinks` to `true`{extra_note}",
156+
))?;
157+
}
158+
159+
Ok(())
160+
}
161+
113162
/// The real git status check starts from here.
114163
fn git(
115164
pkg: &Package,
116165
gctx: &GlobalContext,
117-
src_files: &[PathBuf],
166+
src_files: &[PathEntry],
118167
repo: &git2::Repository,
119168
opts: &PackageOpts<'_>,
120169
) -> CargoResult<Option<GitVcsInfo>> {
@@ -136,6 +185,7 @@ fn git(
136185
let mut dirty_src_files: Vec<_> = src_files
137186
.iter()
138187
.filter(|src_file| dirty_files.iter().any(|path| src_file.starts_with(path)))
188+
.map(|p| p.as_ref())
139189
.chain(dirty_metadata_paths(pkg, repo)?.iter())
140190
.map(|path| {
141191
pathdiff::diff_paths(path, cwd)

src/cargo/ops/vendor.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::core::shell::Verbosity;
22
use crate::core::{GitReference, Package, Workspace};
33
use crate::ops;
44
use crate::sources::path::PathSource;
5+
use crate::sources::PathEntry;
56
use crate::sources::CRATES_IO_REGISTRY;
67
use crate::util::cache_lock::CacheLockMode;
78
use crate::util::{try_canonicalize, CargoResult, GlobalContext};
@@ -315,13 +316,14 @@ fn sync(
315316
fn cp_sources(
316317
pkg: &Package,
317318
src: &Path,
318-
paths: &[PathBuf],
319+
paths: &[PathEntry],
319320
dst: &Path,
320321
cksums: &mut BTreeMap<String, String>,
321322
tmp_buf: &mut [u8],
322323
gctx: &GlobalContext,
323324
) -> CargoResult<()> {
324325
for p in paths {
326+
let p = p.as_ref();
325327
let relative = p.strip_prefix(&src).unwrap();
326328

327329
match relative.to_str() {

src/cargo/sources/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
pub use self::config::SourceConfigMap;
3030
pub use self::directory::DirectorySource;
3131
pub use self::git::GitSource;
32+
pub use self::path::PathEntry;
3233
pub use self::path::PathSource;
3334
pub use self::path::RecursivePathSource;
3435
pub use self::registry::{

0 commit comments

Comments
 (0)