Fix bugs and improve robustness across the codebase
- Fix staged files being silently cleared when commit uses inline files - Refactor step navigation to use direct go_to_step instead of fragile delta math - Change step numbers from i32 to u32 (reject negative values at parse time) - Add tour rm command to mark files for removal during carry-forward - Add tour reset command to clear session and remove tracked files - Consolidate duplicate recursive copy functions into shared copy_tree in utils - Validate step directories are sequential (detect corruption) - Detect binary files in diffs instead of showing garbage - Use /// doc comments on enum variants so clap generates proper help text - Remove custom Help subcommand in favor of clap's built-in --help - Add CorruptedTour error variant for integrity checks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
83
src/utils.rs
83
src/utils.rs
@@ -2,11 +2,54 @@ use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::error::TourError;
|
||||
use crate::SESSION_PATH;
|
||||
use crate::TOUR_DIR;
|
||||
|
||||
pub fn require_tour() -> Result<(), TourError> {
|
||||
if !Path::new(TOUR_DIR).exists() {
|
||||
return Err(TourError::NoTour);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_current_step() -> Option<u32> {
|
||||
fs::read_to_string(SESSION_PATH)
|
||||
.ok()
|
||||
.and_then(|s| {
|
||||
s.split("STEP=")
|
||||
.nth(1)
|
||||
.and_then(|v| v.trim().parse::<u32>().ok())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_tour_step() -> Result<u32, TourError> {
|
||||
let steps_dir = Path::new(TOUR_DIR).join("steps");
|
||||
let mut indices: Vec<u32> = fs::read_dir(&steps_dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.filter_map(|e| e.file_name().to_str()?.parse::<u32>().ok())
|
||||
.collect();
|
||||
|
||||
if indices.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
indices.sort();
|
||||
let count = indices.len() as u32;
|
||||
|
||||
for (i, &idx) in indices.iter().enumerate() {
|
||||
if idx != i as u32 {
|
||||
return Err(TourError::CorruptedTour(
|
||||
format!("step directories are not sequential (expected {}, found {})", i, idx),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Copies a file or directory into dest_dir, preserving relative path structure.
|
||||
/// e.g. `src/main.rs` → `dest_dir/src/main.rs`
|
||||
pub fn copy_path(src: &Path, dest_dir: &Path) -> Result<(), io::Error> {
|
||||
let relative_src = if src.is_absolute() {
|
||||
let cwd = std::env::current_dir()?;
|
||||
@@ -32,6 +75,24 @@ pub fn copy_path(src: &Path, dest_dir: &Path) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively copies src to dest. If src is a directory, copies its contents
|
||||
/// into dest. If src is a file, copies it to dest.
|
||||
pub fn copy_tree(src: &Path, dest: &Path) -> Result<(), io::Error> {
|
||||
if src.is_dir() {
|
||||
fs::create_dir_all(dest)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
copy_tree(&entry.path(), &dest.join(entry.file_name()))?;
|
||||
}
|
||||
} else {
|
||||
if let Some(parent) = dest.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
fs::copy(src, dest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_descendant_of_current_dir(file: &Path) -> Result<bool, io::Error> {
|
||||
is_file_in_dir(file, &std::env::current_dir()?)
|
||||
}
|
||||
@@ -41,23 +102,3 @@ pub fn is_file_in_dir(file: &Path, dir: &Path) -> Result<bool, io::Error> {
|
||||
let dir_canon = dir.canonicalize()?;
|
||||
Ok(file_canon.starts_with(&dir_canon))
|
||||
}
|
||||
|
||||
pub fn get_session_step() -> Result<u32, Box<dyn std::error::Error>> {
|
||||
let session = fs::read_to_string(SESSION_PATH)?;
|
||||
let step = session
|
||||
.split("STEP=")
|
||||
.nth(1)
|
||||
.ok_or("no STEP in session")?
|
||||
.trim()
|
||||
.parse::<u32>()?;
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
pub fn get_tour_step() -> Result<u32, Box<dyn std::error::Error>> {
|
||||
let steps_dir = Path::new(TOUR_DIR).join("steps");
|
||||
let count = fs::read_dir(&steps_dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_dir())
|
||||
.count() as u32;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user