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:
2026-03-05 21:23:39 +00:00
parent 399a72f380
commit 507c61fe5f
19 changed files with 1513 additions and 268 deletions

View File

@@ -2,33 +2,53 @@ use std::io;
use std::path::PathBuf;
#[derive(Debug)]
pub enum CommitError {
NotADescendantOfCurrentDir(PathBuf),
InsideTourDir(PathBuf),
pub enum TourError {
NoTour,
TourAlreadyExists,
TourEnded,
NothingToCommit,
NoSteps,
NotADescendant(PathBuf),
InsideTourDir(PathBuf),
FileNotFound(PathBuf),
StepOutOfRange { step: u32, total: u32 },
CorruptedTour(String),
Io(io::Error),
}
impl std::fmt::Display for CommitError {
impl std::fmt::Display for TourError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotADescendantOfCurrentDir(path) => {
write!(f, "File {:?} is not a descendant of the working directory.", path)
}
Self::InsideTourDir(path) => {
write!(f, "File {:?} is inside a .tour directory, which is not allowed.", path)
Self::NoTour => {
write!(f, "No tour found in this directory. Run `tour init` first.")
}
Self::TourAlreadyExists => write!(f, "A tour already exists in this directory."),
Self::TourEnded => write!(f, "Tour has already been ended."),
Self::NothingToCommit => {
write!(f, "Nothing to commit. Use `tour add <files>` to stage files first.")
}
Self::Io(e) => write!(f, "IO error: {}", e),
Self::NoSteps => {
write!(f, "Cannot end a tour with no steps. Use `tour commit` to add steps first.")
}
Self::NotADescendant(p) => {
write!(f, "File {:?} is not a descendant of the working directory.", p)
}
Self::InsideTourDir(p) => {
write!(f, "File {:?} is inside a .tour directory, which is not allowed.", p)
}
Self::FileNotFound(p) => write!(f, "File not found: {}", p.display()),
Self::StepOutOfRange { step, total } => {
write!(f, "Step {} is out of range (1-{}).", step, total)
}
Self::CorruptedTour(msg) => write!(f, "Tour data is corrupted: {}", msg),
Self::Io(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for CommitError {}
impl std::error::Error for TourError {}
impl From<io::Error> for CommitError {
impl From<io::Error> for TourError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}