-
Notifications
You must be signed in to change notification settings - Fork 1
Workspace Discovery #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,13 @@ | ||
| use std::path::Path; | ||
|
|
||
| use dustfril_core::{ | ||
| analyzer, cleaner, detector, | ||
| models::{CleanupPlan, CleanupResult}, | ||
| }; | ||
|
|
||
| use crate::{cli::CleanArgs, shared::path::resolve_path}; | ||
|
|
||
| // dry-run | ||
| pub fn dry_run() { | ||
| let plan = build_cleanup_plan(); | ||
| pub fn dry_run(args: &CleanArgs) { | ||
| let plan = build_cleanup_plan(args); | ||
|
|
||
| if plan.candidates.is_empty() { | ||
| println!("No cleanup candidates found."); | ||
|
|
@@ -22,8 +22,14 @@ pub fn dry_run() { | |
|
|
||
| use std::io::{self, Write}; | ||
|
|
||
| fn build_cleanup_plan() -> CleanupPlan { | ||
| let scan_result = detector::scan(Path::new(".")); | ||
| fn build_cleanup_plan(args: &CleanArgs) -> CleanupPlan { | ||
| let path = resolve_path(&args.path_args.path); | ||
|
|
||
| let scan_result = if args.path_args.global { | ||
| detector::scan_global() | ||
| } else { | ||
| detector::scan_workspace(&path) | ||
| }; | ||
|
Comment on lines
+25
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent path validation in Unlike 🛠️ Proposed fix-use crate::{cli::CleanArgs, shared::path::resolve_path};
+use crate::{
+ cli::CleanArgs,
+ shared::path::{resolve_path, validate_path},
+};Note: 🤖 Prompt for AI Agents |
||
|
|
||
| let analysis = analyzer::analyze(scan_result); | ||
|
|
||
|
|
@@ -71,7 +77,7 @@ fn print_cleanup_result(result: &CleanupResult) { | |
|
|
||
| println!("Failed: {}", result.failed_paths.len()); | ||
|
|
||
| println!("Freed: {}", analyzer::format_size(result.freed_size_bytes,)); | ||
| println!("Freed: {}", analyzer::format_size(result.freed_size_bytes)); | ||
|
|
||
| if !result.deleted_paths.is_empty() { | ||
| println!("Deleted\n"); | ||
|
|
@@ -84,8 +90,8 @@ fn print_cleanup_result(result: &CleanupResult) { | |
| } | ||
| } | ||
|
|
||
| pub fn execute() { | ||
| let plan = build_cleanup_plan(); | ||
| pub fn execute(args: &CleanArgs) { | ||
| let plan = build_cleanup_plan(args); | ||
|
|
||
| if plan.candidates.is_empty() { | ||
| println!("No cleanup candidates found."); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| pub mod path; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::path::{Path, PathBuf}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn validate_path(path: &Path) -> bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if !path.exists() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eprintln!("Path does not exist: {}", path.display()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Verification inconclusive🏁 Script executed: #!/bin/bash
ast-grep --pattern 'pub fn scan_workspace($$$) { $$$ }'
rg -nP '\bscan_workspace\b' -C3Repository: FrilLab/dustfril Length of output: 3776 Reject non-directory paths in
🛡️ Proposed fix to also require a directory pub fn validate_path(path: &Path) -> bool {
if !path.exists() {
eprintln!("Path does not exist: {}", path.display());
return false;
}
+ if !path.is_dir() {
+ eprintln!("Path is not a directory: {}", path.display());
+
+ return false;
+ }
+
true
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn resolve_path(path: &Option<PathBuf>) -> PathBuf { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| path.clone() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory")) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,8 @@ | ||
| mod cargo_project; | ||
| mod git; | ||
| mod registry; | ||
| mod project; | ||
| mod rust; | ||
| mod scan; | ||
| mod target; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| pub use scan::{scan, scan_global, scan_project}; | ||
| pub use scan::{scan, scan_global, scan_project, scan_workspace}; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,53 @@ | ||||||||||||||||||||||||||||||||||
| use std::{ | ||||||||||||||||||||||||||||||||||
| fs, | ||||||||||||||||||||||||||||||||||
| path::{Path, PathBuf}, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||||||||||||||||||||||||||||||
| // pub enum Ecosystem { | ||||||||||||||||||||||||||||||||||
| // Rust, | ||||||||||||||||||||||||||||||||||
| // Node, | ||||||||||||||||||||||||||||||||||
| // Java, | ||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| #[derive(Debug, Clone)] | ||||||||||||||||||||||||||||||||||
| pub struct Project { | ||||||||||||||||||||||||||||||||||
| pub root: PathBuf, | ||||||||||||||||||||||||||||||||||
| // pub ecosystem: Ecosystem, | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| pub fn find_projects(root: &Path) -> Vec<Project> { | ||||||||||||||||||||||||||||||||||
| let mut projects = Vec::new(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| visit(root, &mut projects); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| projects | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// Cargo project detection and artifact scanning. | ||||||||||||||||||||||||||||||||||
| pub fn is_cargo_project(root: &Path) -> bool { | ||||||||||||||||||||||||||||||||||
| root.join("Cargo.toml").is_file() | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fn visit(dir: &Path, projects: &mut Vec<Project>) { | ||||||||||||||||||||||||||||||||||
| if is_cargo_project(dir) { | ||||||||||||||||||||||||||||||||||
| projects.push(Project { | ||||||||||||||||||||||||||||||||||
| root: dir.to_path_buf(), | ||||||||||||||||||||||||||||||||||
| // ecosystem: Ecosystem::Rust, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirm that stopping descent at the first
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let Ok(entries) = fs::read_dir(dir) else { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| for entry in entries.flatten() { | ||||||||||||||||||||||||||||||||||
| let path = entry.path(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if path.is_dir() { | ||||||||||||||||||||||||||||||||||
| visit(&path, projects); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, std::fs::DirEntry::file_type does not follow symlinks [1][2]. When called on a DirEntry that points to a symbolic link, it returns the file type of the link itself, rather than the file type of the link's target [1][2]. Because FileType::is_dir is mutually exclusive to FileType::is_symlink [3][4], it will return false if the entry is a symbolic link, even if that link points to a directory [3][4]. In summary: 1. DirEntry::file_type returns the file type of the symlink itself, not the target [1][2]. 2. FileType::is_dir checks if the file type is a directory [3]. Since a symlink is classified as a symlink (and not a directory) by the FileType methods, is_dir returns false for symlinks [3][4]. 3. To determine if a symlink points to a directory, you would typically need to check if the entry is a symlink, and if so, potentially resolve the path or use alternative methods to inspect the target [5]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="crates/dustfril-core/src/detector/project.rs"
echo "---- $FILE ----"
nl -ba "$FILE" | sed -n '1,140p'
echo "---- search for is_dir/metadata usage in file ----"
rg -n "is_dir\(|file_type\(|symlink|read_link|follow" "$FILE" || true
echo "---- locate visit() definition and how recursion is handled ----"
rg -n "fn visit|visit\(" "$FILE"Repository: FrilLab/dustfril Length of output: 176 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="crates/dustfril-core/src/detector/project.rs"
echo "---- $FILE (first 200 lines with line numbers) ----"
cat -n "$FILE" | sed -n '1,220p'
echo "---- search for is_dir/file_type/symlink usage in file ----"
rg -n "is_dir\(|file_type\(|symlink|read_link|follow" "$FILE" || true
echo "---- locate visit() definition and its recursion ----"
rg -n "fn visit|visit\(" "$FILE" || trueRepository: FrilLab/dustfril Length of output: 1784 🌐 Web query:
💡 Result: Rust’s std::path::Path::is_dir does follow symlinks. In the Rust docs for Path::is_dir, it states that the function “will traverse symbolic links to query information about the destination file” and returns true when the path exists and is pointing at a directory [1]. By contrast, std::path::Path::symlink_metadata queries metadata “without following symlinks” [1]. If you want to test whether a symlink itself (not its target) is a directory, the usual approach is: - call symlink_metadata, then inspect whether the resulting metadata represents a symlink (via Metadata::is_symlink / file_type), rather than using is_dir which follows the link [1][2]. Summary: - Path::is_dir -> follows symlinks (checks target) [1] - Path::symlink_metadata -> does not follow symlinks (checks link itself) [1] Citations:
Avoid symlink-recursion in system-wide project scanning 🛡️ Proposed fix to skip symlinks for entry in entries.flatten() {
- let path = entry.path();
-
- if path.is_dir() {
- visit(&path, projects);
- }
+ let Ok(file_type) = entry.file_type() else {
+ continue;
+ };
+
+ if file_type.is_dir() {
+ visit(&entry.path(), projects);
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| pub mod git; | ||
| pub mod registry; | ||
| pub mod target; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 2097
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 346
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 42
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 502
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 1827
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 1010
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 330
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 5937
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 3395
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 158
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 42
🏁 Script executed:
Repository: FrilLab/dustfril
Length of output: 42
Fix
--globalvs workspace semantics to match flag documentation--globalcurrently dispatches todetector::scan_global()only (Cargo registry + git under$HOME/.cargo), while non---globaldispatches todetector::scan_workspace(&path)only (per-project artifacts discovered under the provided path).PathArgs.globalsays “Scan the entire system…”, butscan_global()doesn’t discover Cargo projects across the system; project artifacts only appear when--globalis false.--globalis meant to include both project + global artifacts, route todetector::scan(&path); otherwise update the docstring to reflect “global registry/git artifacts only”.🤖 Prompt for AI Agents