mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
fix(package): warn if symlinks checked out as plain text files
`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]: <f1241afcc7/setup.c (L2394-L2403)
>
[`core.symlinks`]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks
This commit is contained in:
parent
871b17f59a
commit
059fe16085
@ -92,6 +92,8 @@ pub fn check_repo_state(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn_symlink_checked_out_as_plain_text_file(gctx, src_files, &repo)?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"found (git) Cargo.toml at `{}` in workdir `{}`",
|
"found (git) Cargo.toml at `{}` in workdir `{}`",
|
||||||
path.display(),
|
path.display(),
|
||||||
@ -111,6 +113,52 @@ pub fn check_repo_state(
|
|||||||
return Ok(Some(VcsInfo { git, path_in_vcs }));
|
return Ok(Some(VcsInfo { git, path_in_vcs }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Warns if any symlinks were checked out as plain text files.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
fn warn_symlink_checked_out_as_plain_text_file(
|
||||||
|
gctx: &GlobalContext,
|
||||||
|
src_files: &[PathEntry],
|
||||||
|
repo: &git2::Repository,
|
||||||
|
) -> CargoResult<()> {
|
||||||
|
if repo
|
||||||
|
.config()
|
||||||
|
.and_then(|c| c.get_bool("core.symlinks"))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if src_files.iter().any(|f| f.maybe_plain_text_symlink()) {
|
||||||
|
let mut shell = gctx.shell();
|
||||||
|
shell.warn(format_args!(
|
||||||
|
"found symbolic links that may be checked out as regular files for git repo at `{}`\n\
|
||||||
|
This might cause the `.crate` file to include incorrect or incomplete files",
|
||||||
|
repo.workdir().unwrap().display(),
|
||||||
|
))?;
|
||||||
|
let extra_note = if cfg!(windows) {
|
||||||
|
"\nAnd on Windows, enable the Developer Mode to support symlinks"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
shell.note(format_args!(
|
||||||
|
"to avoid this, set the Git config `core.symlinks` to `true`{extra_note}",
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// The real git status check starts from here.
|
/// The real git status check starts from here.
|
||||||
fn git(
|
fn git(
|
||||||
pkg: &Package,
|
pkg: &Package,
|
||||||
|
@ -407,7 +407,7 @@ impl<'gctx> Source for RecursivePathSource<'gctx> {
|
|||||||
/// Type that abstracts over [`gix::dir::entry::Kind`] and [`fs::FileType`].
|
/// Type that abstracts over [`gix::dir::entry::Kind`] and [`fs::FileType`].
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum FileType {
|
enum FileType {
|
||||||
File,
|
File { maybe_symlink: bool },
|
||||||
Dir,
|
Dir,
|
||||||
Symlink,
|
Symlink,
|
||||||
Other,
|
Other,
|
||||||
@ -416,7 +416,9 @@ enum FileType {
|
|||||||
impl From<fs::FileType> for FileType {
|
impl From<fs::FileType> for FileType {
|
||||||
fn from(value: fs::FileType) -> Self {
|
fn from(value: fs::FileType) -> Self {
|
||||||
if value.is_file() {
|
if value.is_file() {
|
||||||
FileType::File
|
FileType::File {
|
||||||
|
maybe_symlink: false,
|
||||||
|
}
|
||||||
} else if value.is_dir() {
|
} else if value.is_dir() {
|
||||||
FileType::Dir
|
FileType::Dir
|
||||||
} else if value.is_symlink() {
|
} else if value.is_symlink() {
|
||||||
@ -432,7 +434,9 @@ impl From<gix::dir::entry::Kind> for FileType {
|
|||||||
use gix::dir::entry::Kind;
|
use gix::dir::entry::Kind;
|
||||||
match value {
|
match value {
|
||||||
Kind::Untrackable => FileType::Other,
|
Kind::Untrackable => FileType::Other,
|
||||||
Kind::File => FileType::File,
|
Kind::File => FileType::File {
|
||||||
|
maybe_symlink: false,
|
||||||
|
},
|
||||||
Kind::Symlink => FileType::Symlink,
|
Kind::Symlink => FileType::Symlink,
|
||||||
Kind::Directory | Kind::Repository => FileType::Dir,
|
Kind::Directory | Kind::Repository => FileType::Dir,
|
||||||
}
|
}
|
||||||
@ -454,7 +458,7 @@ impl PathEntry {
|
|||||||
/// Similar to [`std::path::Path::is_file`]
|
/// Similar to [`std::path::Path::is_file`]
|
||||||
/// but doesn't follow the symbolic link nor make any system call
|
/// but doesn't follow the symbolic link nor make any system call
|
||||||
pub fn is_file(&self) -> bool {
|
pub fn is_file(&self) -> bool {
|
||||||
matches!(self.ty, FileType::File)
|
matches!(self.ty, FileType::File { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Similar to [`std::path::Path::is_dir`]
|
/// Similar to [`std::path::Path::is_dir`]
|
||||||
@ -468,6 +472,19 @@ impl PathEntry {
|
|||||||
pub fn is_symlink(&self) -> bool {
|
pub fn is_symlink(&self) -> bool {
|
||||||
matches!(self.ty, FileType::Symlink)
|
matches!(self.ty, FileType::Symlink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this path might be a plain text symlink.
|
||||||
|
///
|
||||||
|
/// Git may check out symlinks as plain text files that contain the link texts,
|
||||||
|
/// when either `core.symlinks` is `false`, or on Windows.
|
||||||
|
pub fn maybe_plain_text_symlink(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.ty,
|
||||||
|
FileType::File {
|
||||||
|
maybe_symlink: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for PathEntry {
|
impl std::ops::Deref for PathEntry {
|
||||||
@ -713,7 +730,24 @@ fn list_files_gix(
|
|||||||
&& item.entry.rela_path == "Cargo.lock")
|
&& item.entry.rela_path == "Cargo.lock")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|res| res.map(|item| (item.entry.rela_path, item.entry.disk_kind)))
|
.map(|res| {
|
||||||
|
res.map(|item| {
|
||||||
|
// Assumption: if a file tracked as a symlink in Git index, and
|
||||||
|
// the actual file type on disk is file, then it might be a
|
||||||
|
// plain text file symlink.
|
||||||
|
// There are exceptions like the file has changed from a symlink
|
||||||
|
// to a real text file, but hasn't been committed to Git index.
|
||||||
|
// Exceptions may be rare so we're okay with this now.
|
||||||
|
let maybe_plain_text_symlink = item.entry.index_kind
|
||||||
|
== Some(gix::dir::entry::Kind::Symlink)
|
||||||
|
&& item.entry.disk_kind == Some(gix::dir::entry::Kind::File);
|
||||||
|
(
|
||||||
|
item.entry.rela_path,
|
||||||
|
item.entry.disk_kind,
|
||||||
|
maybe_plain_text_symlink,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
.chain(
|
.chain(
|
||||||
// Append entries that might be tracked in `<pkg_root>/target/`.
|
// Append entries that might be tracked in `<pkg_root>/target/`.
|
||||||
index
|
index
|
||||||
@ -731,12 +765,13 @@ fn list_files_gix(
|
|||||||
// This traversal is not part of a `status()`, and tracking things in `target/`
|
// This traversal is not part of a `status()`, and tracking things in `target/`
|
||||||
// is rare.
|
// is rare.
|
||||||
None,
|
None,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(Ok),
|
.map(Ok),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
let (rela_path, kind) = item?;
|
let (rela_path, kind, maybe_plain_text_symlink) = item?;
|
||||||
let file_path = root.join(gix::path::from_bstr(rela_path));
|
let file_path = root.join(gix::path::from_bstr(rela_path));
|
||||||
if file_path.file_name().and_then(|name| name.to_str()) == Some("Cargo.toml") {
|
if file_path.file_name().and_then(|name| name.to_str()) == Some("Cargo.toml") {
|
||||||
// Keep track of all sub-packages found and also strip out all
|
// Keep track of all sub-packages found and also strip out all
|
||||||
@ -781,9 +816,16 @@ fn list_files_gix(
|
|||||||
} else if (filter)(&file_path, is_dir) {
|
} else if (filter)(&file_path, is_dir) {
|
||||||
assert!(!is_dir);
|
assert!(!is_dir);
|
||||||
trace!(" found {}", file_path.display());
|
trace!(" found {}", file_path.display());
|
||||||
|
let ty = match kind.map(Into::into) {
|
||||||
|
Some(FileType::File { .. }) => FileType::File {
|
||||||
|
maybe_symlink: maybe_plain_text_symlink,
|
||||||
|
},
|
||||||
|
Some(ty) => ty,
|
||||||
|
None => FileType::Other,
|
||||||
|
};
|
||||||
files.push(PathEntry {
|
files.push(PathEntry {
|
||||||
path: file_path,
|
path: file_path,
|
||||||
ty: kind.map(Into::into).unwrap_or(FileType::Other),
|
ty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7077,6 +7077,10 @@ fn git_core_symlinks_false() {
|
|||||||
|
|
||||||
p.cargo("package --allow-dirty")
|
p.cargo("package --allow-dirty")
|
||||||
.with_stderr_data(str![[r#"
|
.with_stderr_data(str![[r#"
|
||||||
|
[WARNING] found symbolic links that may be checked out as regular files for git repo at `[ROOT]/foo/`
|
||||||
|
This might cause the `.crate` file to include incorrect or incomplete files
|
||||||
|
[NOTE] to avoid this, set the Git config `core.symlinks` to `true`
|
||||||
|
...
|
||||||
[PACKAGING] bar v0.0.0 ([ROOT]/foo)
|
[PACKAGING] bar v0.0.0 ([ROOT]/foo)
|
||||||
[PACKAGED] 7 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
[PACKAGED] 7 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||||
[VERIFYING] bar v0.0.0 ([ROOT]/foo)
|
[VERIFYING] bar v0.0.0 ([ROOT]/foo)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user