Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/oxide/src/scanner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ impl Scanner {
}
}

// Forget files that were tracked during a previous scan but no longer
// appear in the current source walk.
self.files.retain(|file| seen_files.contains(file));
self.mtimes.retain(|file, _| seen_files.contains(file));
Comment on lines +429 to +430
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale dirs and extensions sets after file deletion

The PR correctly prunes self.files and self.mtimes when files disappear, but self.dirs and self.extensions are only ever inserted into and never pruned. If all files inside a directory (or all files of a given extension) are deleted, those entries remain in self.dirs / self.extensions. This affects get_globs() for Auto/External sources, which calls resolve_globs(base, &self.dirs, &self.extensions) — stale directories and extensions will produce watch-glob patterns for paths that no longer exist on disk.


// Read + preprocess all discovered files in parallel
let scanned_blobs: Vec<Vec<u8>> = content_paths
.into_par_iter()
Expand Down
51 changes: 51 additions & 0 deletions crates/oxide/tests/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ mod scanner {
scan_with_globs(paths_with_content, vec!["@source '**/*'"])
}

fn scanned_files(scanner: &mut Scanner, base: &Path) -> Vec<String> {
let base_dir =
format!("{}{}", dunce::canonicalize(base).unwrap().display(), "/").replace('\\', "/");

let mut files = scanner
.get_files()
.iter()
// Normalize paths to use unix style separators
.map(|file| file.replace('\\', "/").replace(&base_dir, ""))
.collect::<Vec<_>>();
files.sort();
files
}

#[test]
fn it_should_work_with_a_set_of_root_files() {
let ScanResult {
Expand Down Expand Up @@ -840,6 +854,43 @@ mod scanner {
);
}

#[test]
fn it_should_stop_tracking_deleted_files() {
// Create a temporary working directory
let dir = tempdir().unwrap().into_path();

// Initialize this directory as a git repository
let _ = Command::new("git").arg("init").current_dir(&dir).output();

// Create files
create_files_in(
&dir,
&[
("src/index.html", "content-['src/index.html']"),
("src/assets/icon.svg", "<svg></svg>"),
],
);

let sources = vec![public_source_entry_from_pattern(
dir.clone(),
"@source '**/*'",
)];

let mut scanner = Scanner::new(sources);
let _ = scanner.scan();

assert_eq!(
scanned_files(&mut scanner, &dir),
vec!["src/assets/icon.svg", "src/index.html"]
);

fs::remove_file(dir.join("src/assets/icon.svg")).unwrap();

let _ = scanner.scan();

assert_eq!(scanned_files(&mut scanner, &dir), vec!["src/index.html"]);
}

#[test]
fn it_should_ignore_negated_custom_sources() {
let ScanResult {
Expand Down