Skip to content

Commit e3ef30a

Browse files
committed
feat: spare symlinks from deduplication
1 parent 7f356bb commit e3ef30a

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

src/app.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ impl App {
136136
// Hardlinks deduplication doesn't work properly if there are more than 1 paths pointing to
137137
// the same tree or if a path points to a subtree of another path. Therefore, we must find
138138
// and remove such duplications before they cause problem.
139-
//
140-
// The current implementation has a quirk in which symbolic link to the real path of another
141-
// argument would disappear. However, considering that nobody would use pdu to measure mere
142-
// symbolic links, we trade this bug for a simpler codebase.
143139
use deduplicate_arguments::{deduplicate_arguments, RealApi};
144140
deduplicate_arguments::<RealApi>(&mut self.args.files);
145141
}

src/app/deduplicate_arguments.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
use pipe_trait::Pipe;
2-
use std::{collections::HashSet, fs::canonicalize, io, mem::take, path::PathBuf};
2+
use std::{
3+
collections::HashSet,
4+
fs::{canonicalize, symlink_metadata},
5+
io,
6+
mem::take,
7+
path::PathBuf,
8+
};
39

410
/// Mockable APIs to interact with the system.
511
pub trait Api {
612
type Argument;
713
type RealPath: Eq;
814
type RealPathError;
915
fn canonicalize(path: &Self::Argument) -> Result<Self::RealPath, Self::RealPathError>;
16+
fn is_real_dir(path: &Self::Argument) -> bool;
1017
fn starts_with(a: &Self::RealPath, b: &Self::RealPath) -> bool;
1118
}
1219

@@ -21,6 +28,11 @@ impl Api for RealApi {
2128
canonicalize(path)
2229
}
2330

31+
fn is_real_dir(path: &Self::Argument) -> bool {
32+
path.pipe(symlink_metadata)
33+
.is_ok_and(|metadata| !metadata.is_symlink() && metadata.is_dir())
34+
}
35+
2436
fn starts_with(a: &Self::RealPath, b: &Self::RealPath) -> bool {
2537
a.starts_with(b)
2638
}
@@ -43,13 +55,20 @@ pub fn deduplicate_arguments<Api: self::Api>(arguments: &mut Vec<Api::Argument>)
4355
pub fn find_argument_duplications_to_remove<Api: self::Api>(
4456
arguments: &[Api::Argument],
4557
) -> HashSet<usize> {
46-
let real_paths: Vec<_> = arguments.iter().map(Api::canonicalize).collect();
58+
let real_paths: Vec<_> = arguments
59+
.iter()
60+
.map(|path| {
61+
Api::is_real_dir(path)
62+
.then(|| Api::canonicalize(path))
63+
.and_then(Result::ok)
64+
})
65+
.collect();
4766
assert_eq!(arguments.len(), real_paths.len());
4867

4968
let mut to_remove = HashSet::new();
5069
for left_index in 0..arguments.len() {
5170
for right_index in (left_index + 1)..arguments.len() {
52-
if let (Ok(left), Ok(right)) = (&real_paths[left_index], &real_paths[right_index]) {
71+
if let (Some(left), Some(right)) = (&real_paths[left_index], &real_paths[right_index]) {
5372
// both paths are the same, remove the second one
5473
if left == right {
5574
to_remove.insert(right_index);
@@ -148,6 +167,13 @@ mod tests {
148167
.pipe(Ok)
149168
}
150169

170+
fn is_real_dir(path: &Self::Argument) -> bool {
171+
let path = PathBuf::from(path).normalize();
172+
MOCKED_SYMLINKS
173+
.iter()
174+
.all(|(link, _)| PathBuf::from(link).normalize() != path)
175+
}
176+
151177
fn starts_with(a: &Self::RealPath, b: &Self::RealPath) -> bool {
152178
a.starts_with(b)
153179
}

0 commit comments

Comments
 (0)