diff options
| author | diogo464 <[email protected]> | 2025-06-26 16:29:05 +0100 |
|---|---|---|
| committer | diogo464 <[email protected]> | 2025-06-26 16:29:05 +0100 |
| commit | 397fff2b66d743d9f3dc71061558637844c28975 (patch) | |
| tree | 7b5cf305ba8fea968a487371c57fde4b3fcbc7f3 /src/main.rs | |
| parent | 8b71976bbc17bb33e0a2cbb302d5f4aa2a7ebd34 (diff) | |
Improve root directory validation with enhanced error handlingfix-root-directory-validation
Enhanced root directory validation with several improvements:
1. **Better Error Messages**: More descriptive error messages with specific
guidance on how to resolve issues (create directory, check permissions, etc.)
2. **Path Canonicalization**: Resolve symlinks and relative paths to absolute
canonical paths, ensuring consistent behavior across different path formats
3. **Write Permission Validation**: Proactively check write permissions by
creating a temporary test file before attempting daemon operations
4. **Comprehensive Edge Case Handling**:
- Broken symlinks are properly detected as non-existent
- Regular symlinks to directories are resolved correctly
- Symlinks to files are rejected appropriately
- Deep nested non-existent paths are handled gracefully
5. **Enhanced Git Root Handling**: Better error messages for git repository
detection failures and .demon directory creation issues
6. **Robust Testing**: Added comprehensive test suite covering:
- All edge cases and error conditions
- Improved error message validation
- Write permission validation
- Path canonicalization behavior
- Symlink handling (valid, broken, to files, to directories)
The validation now provides clear, actionable feedback to users when
directory issues are encountered, making the tool more user-friendly
and robust in various deployment scenarios.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 85 |
1 files changed, 67 insertions, 18 deletions
diff --git a/src/main.rs b/src/main.rs index d0a2763..9f3dc3c 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -332,7 +332,9 @@ fn run_command(command: Commands) -> Result<()> { | |||
| 332 | } | 332 | } |
| 333 | 333 | ||
| 334 | fn find_git_root() -> Result<PathBuf> { | 334 | fn find_git_root() -> Result<PathBuf> { |
| 335 | let mut current = std::env::current_dir()?; | 335 | let mut current = std::env::current_dir().with_context( |
| 336 | || "Failed to get current working directory. Please check your file system permissions", | ||
| 337 | )?; | ||
| 336 | 338 | ||
| 337 | // Find the git root directory | 339 | // Find the git root directory |
| 338 | let git_root = loop { | 340 | let git_root = loop { |
| @@ -345,7 +347,10 @@ fn find_git_root() -> Result<PathBuf> { | |||
| 345 | Some(parent) => current = parent.to_path_buf(), | 347 | Some(parent) => current = parent.to_path_buf(), |
| 346 | None => { | 348 | None => { |
| 347 | return Err(anyhow::anyhow!( | 349 | return Err(anyhow::anyhow!( |
| 348 | "No git repository found. Please specify --root-dir or run from within a git repository" | 350 | "No git repository found in current directory or any parent directories.\n\ |
| 351 | Please either:\n\ | ||
| 352 | 1. Run demon from within a git repository, or\n\ | ||
| 353 | 2. Specify a root directory with --root-dir <path>" | ||
| 349 | )); | 354 | )); |
| 350 | } | 355 | } |
| 351 | } | 356 | } |
| @@ -358,17 +363,30 @@ fn find_git_root() -> Result<PathBuf> { | |||
| 358 | if demon_dir.exists() { | 363 | if demon_dir.exists() { |
| 359 | if !demon_dir.is_dir() { | 364 | if !demon_dir.is_dir() { |
| 360 | return Err(anyhow::anyhow!( | 365 | return Err(anyhow::anyhow!( |
| 361 | "Path {} exists but is not a directory. Please remove it or specify --root-dir", | 366 | "Path {} exists but is not a directory.\n\ |
| 367 | Please either:\n\ | ||
| 368 | 1. Remove the existing file: rm {}\n\ | ||
| 369 | 2. Specify a different root directory with --root-dir <path>", | ||
| 370 | demon_dir.display(), | ||
| 362 | demon_dir.display() | 371 | demon_dir.display() |
| 363 | )); | 372 | )); |
| 364 | } | 373 | } |
| 365 | // .demon exists and is a directory, we can use it | 374 | // .demon exists and is a directory, we can use it |
| 375 | tracing::debug!("Using existing daemon directory: {}", demon_dir.display()); | ||
| 366 | return Ok(demon_dir); | 376 | return Ok(demon_dir); |
| 367 | } | 377 | } |
| 368 | 378 | ||
| 369 | // Create .demon directory | 379 | // Create .demon directory |
| 370 | std::fs::create_dir(&demon_dir) | 380 | std::fs::create_dir(&demon_dir).with_context(|| { |
| 371 | .with_context(|| format!("Failed to create daemon directory {}", demon_dir.display()))?; | 381 | format!( |
| 382 | "Failed to create daemon directory {}.\n\ | ||
| 383 | This may be due to:\n\ | ||
| 384 | 1. Insufficient permissions in the git root directory\n\ | ||
| 385 | 2. File system errors\n\ | ||
| 386 | Please check permissions or specify --root-dir with a writable directory", | ||
| 387 | demon_dir.display() | ||
| 388 | ) | ||
| 389 | })?; | ||
| 372 | 390 | ||
| 373 | tracing::info!("Created daemon directory: {}", demon_dir.display()); | 391 | tracing::info!("Created daemon directory: {}", demon_dir.display()); |
| 374 | 392 | ||
| @@ -378,24 +396,55 @@ fn find_git_root() -> Result<PathBuf> { | |||
| 378 | fn resolve_root_dir(global: &Global) -> Result<PathBuf> { | 396 | fn resolve_root_dir(global: &Global) -> Result<PathBuf> { |
| 379 | match &global.root_dir { | 397 | match &global.root_dir { |
| 380 | Some(dir) => { | 398 | Some(dir) => { |
| 381 | if !dir.exists() { | 399 | // Validate the specified root directory |
| 382 | return Err(anyhow::anyhow!( | 400 | validate_root_directory(dir) |
| 383 | "Specified root directory does not exist: {}", | ||
| 384 | dir.display() | ||
| 385 | )); | ||
| 386 | } | ||
| 387 | if !dir.is_dir() { | ||
| 388 | return Err(anyhow::anyhow!( | ||
| 389 | "Specified root path is not a directory: {}", | ||
| 390 | dir.display() | ||
| 391 | )); | ||
| 392 | } | ||
| 393 | Ok(dir.clone()) | ||
| 394 | } | 401 | } |
| 395 | None => find_git_root(), | 402 | None => find_git_root(), |
| 396 | } | 403 | } |
| 397 | } | 404 | } |
| 398 | 405 | ||
| 406 | /// Validates that a directory path is suitable for use as a root directory | ||
| 407 | fn validate_root_directory(dir: &Path) -> Result<PathBuf> { | ||
| 408 | // First check if the path exists | ||
| 409 | if !dir.exists() { | ||
| 410 | return Err(anyhow::anyhow!( | ||
| 411 | "Specified root directory does not exist: {}\nPlease create the directory first or specify a different path", | ||
| 412 | dir.display() | ||
| 413 | )); | ||
| 414 | } | ||
| 415 | |||
| 416 | // Check if it's actually a directory | ||
| 417 | if !dir.is_dir() { | ||
| 418 | return Err(anyhow::anyhow!( | ||
| 419 | "Specified root path is not a directory: {}\nPlease specify a directory path, not a file", | ||
| 420 | dir.display() | ||
| 421 | )); | ||
| 422 | } | ||
| 423 | |||
| 424 | // Try to canonicalize the path to resolve symlinks and make it absolute | ||
| 425 | let canonical_dir = dir.canonicalize().with_context(|| { | ||
| 426 | format!( | ||
| 427 | "Failed to resolve path {}: path may contain invalid components or broken symlinks", | ||
| 428 | dir.display() | ||
| 429 | ) | ||
| 430 | })?; | ||
| 431 | |||
| 432 | // Check if we can write to the directory by attempting to create a temporary file | ||
| 433 | let temp_file_path = canonical_dir.join(".demon_write_test"); | ||
| 434 | if let Err(e) = std::fs::write(&temp_file_path, "test") { | ||
| 435 | return Err(anyhow::anyhow!( | ||
| 436 | "Cannot write to specified root directory {}: {}\nPlease check directory permissions", | ||
| 437 | canonical_dir.display(), | ||
| 438 | e | ||
| 439 | )); | ||
| 440 | } | ||
| 441 | // Clean up the test file | ||
| 442 | let _ = std::fs::remove_file(&temp_file_path); | ||
| 443 | |||
| 444 | tracing::debug!("Validated root directory: {}", canonical_dir.display()); | ||
| 445 | Ok(canonical_dir) | ||
| 446 | } | ||
| 447 | |||
| 399 | fn build_file_path(root_dir: &Path, id: &str, extension: &str) -> PathBuf { | 448 | fn build_file_path(root_dir: &Path, id: &str, extension: &str) -> PathBuf { |
| 400 | root_dir.join(format!("{id}.{extension}")) | 449 | root_dir.join(format!("{id}.{extension}")) |
| 401 | } | 450 | } |
