Skip to content

Commit aaac688

Browse files
committed
refactor(source): preserve whether a path is under a symlink dir
This is helpful for VCS status check. Paths emitted by PathSource are always under package root, We lose the track of file type info of paths under symlink dirs, so we need this extra bit of information.
1 parent 081545f commit aaac688

File tree

1 file changed

+40
-0
lines changed

1 file changed

+40
-0
lines changed

src/cargo/sources/path.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ impl From<gix::dir::entry::Kind> for FileType {
448448
pub struct PathEntry {
449449
path: PathBuf,
450450
ty: FileType,
451+
/// Whether this path was visited when traversing a symlink directory.
452+
under_symlink_dir: bool,
451453
}
452454

453455
impl PathEntry {
@@ -469,10 +471,21 @@ impl PathEntry {
469471

470472
/// Similar to [`std::path::Path::is_symlink`]
471473
/// but doesn't follow the symbolic link nor make any system call
474+
///
475+
/// If the path is not a symlink but under a symlink parent directory,
476+
/// this will return false.
477+
/// See [`PathEntry::is_symlink_or_under_symlink`] for an alternative.
472478
pub fn is_symlink(&self) -> bool {
473479
matches!(self.ty, FileType::Symlink)
474480
}
475481

482+
/// Whether a path is a symlink or a path under a symlink directory.
483+
///
484+
/// Use [`PathEntry::is_symlink`] to get the exact file type of the path only.
485+
pub fn is_symlink_or_under_symlink(&self) -> bool {
486+
self.is_symlink() || self.under_symlink_dir
487+
}
488+
476489
/// Whether this path might be a plain text symlink.
477490
///
478491
/// Git may check out symlinks as plain text files that contain the link texts,
@@ -826,6 +839,9 @@ fn list_files_gix(
826839
files.push(PathEntry {
827840
path: file_path,
828841
ty,
842+
// Git index doesn't include files from symlink diretory,
843+
// symlink dirs are handled in `list_files_walk`.
844+
under_symlink_dir: false,
829845
});
830846
}
831847
}
@@ -847,6 +863,10 @@ fn list_files_walk(
847863
) -> CargoResult<()> {
848864
let walkdir = WalkDir::new(path)
849865
.follow_links(true)
866+
// While this is the default, set it explicitly.
867+
// We need walkdir to visit the directory tree in depth-first order,
868+
// so we can ensure a path visited later be under a certain directory.
869+
.contents_first(false)
850870
.into_iter()
851871
.filter_entry(|entry| {
852872
let path = entry.path();
@@ -876,10 +896,27 @@ fn list_files_walk(
876896

877897
true
878898
});
899+
900+
let mut current_symlink_dir = None;
879901
for entry in walkdir {
880902
match entry {
881903
Ok(entry) => {
882904
let file_type = entry.file_type();
905+
906+
match current_symlink_dir.as_ref() {
907+
Some(dir) if entry.path().starts_with(dir) => {
908+
// Still walk under the same parent symlink dir, so keep it
909+
}
910+
Some(_) | None => {
911+
// Not under any parent symlink dir, update the current one.
912+
current_symlink_dir = if file_type.is_dir() && entry.path_is_symlink() {
913+
Some(entry.path().to_path_buf())
914+
} else {
915+
None
916+
};
917+
}
918+
}
919+
883920
if file_type.is_file() || file_type.is_symlink() {
884921
// We follow_links(true) here so check if entry was created from a symlink
885922
let ty = if entry.path_is_symlink() {
@@ -890,6 +927,8 @@ fn list_files_walk(
890927
ret.push(PathEntry {
891928
path: entry.into_path(),
892929
ty,
930+
// This rely on contents_first(false), which walks in depth-first order
931+
under_symlink_dir: current_symlink_dir.is_some(),
893932
});
894933
}
895934
}
@@ -907,6 +946,7 @@ fn list_files_walk(
907946
Some(path) => ret.push(PathEntry {
908947
path: path.to_path_buf(),
909948
ty: FileType::Other,
949+
under_symlink_dir: false,
910950
}),
911951
None => return Err(err.into()),
912952
},

0 commit comments

Comments
 (0)