use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
use crate::channel::{DEFAULT_REVIEW_CHANNEL, ReviewChannel, ReviewChannelCandidate};
use crate::commands::{
DirtyPathHandling, Gate, MarkOptions, StatusMode, diff_path, mark_paths, status, unmark_paths,
};
use crate::error::AppError;
use crate::git::Git;
use crate::git_ref_format::{CheckRefFormatError, check_ref_format};
use crate::notes::{GitNotesStore, NotesStore};
#[derive(Parser, Debug)]
#[command(
name = "git-vet",
version,
about = "Track human review state for Git-tracked file contents"
)]
pub struct Cli {
#[arg(long, global = true, default_value = DEFAULT_REVIEW_CHANNEL)]
channel: String,
#[command(subcommand)]
command: CommandKind,
}
#[derive(Subcommand, Debug)]
enum CommandKind {
Mark {
#[arg(long)]
allow_dirty: bool,
#[arg(required = true)]
paths: Vec<PathBuf>,
},
Unmark {
#[arg(required = true)]
paths: Vec<PathBuf>,
},
Status {
#[arg(long)]
json: bool,
#[arg(long)]
all: bool,
#[arg(long)]
check: bool,
},
Diff { path: PathBuf },
Sync {
#[arg(long)]
remote: Option<String>,
},
Prune,
}
pub fn run_cli() -> Result<ExitCode, AppError> {
let cli = Cli::parse();
let channel = review_channel_from_cli(&cli.channel)?;
let git = Git::discover()?;
let notes = GitNotesStore::new(&git, channel.notes_ref().clone());
match cli.command {
CommandKind::Mark { paths, allow_dirty } => {
let dirty_paths = if allow_dirty {
DirtyPathHandling::Allow
} else {
DirtyPathHandling::Prompt
};
mark_paths(&git, ¬es, &paths, MarkOptions { dirty_paths })?;
Ok(ExitCode::SUCCESS)
}
CommandKind::Unmark { paths } => {
unmark_paths(&git, ¬es, &paths)?;
Ok(ExitCode::SUCCESS)
}
CommandKind::Status { json, all, check } => {
match status(&git, ¬es, &channel, StatusMode { json, all, check })? {
Gate::Open => Ok(ExitCode::SUCCESS),
Gate::Closed => Ok(ExitCode::from(1)),
}
}
CommandKind::Diff { path } => {
diff_path(&git, ¬es, &path)?;
Ok(ExitCode::SUCCESS)
}
CommandKind::Sync { remote } => {
let remote = git.select_sync_remote(remote.as_deref())?;
notes.sync(&remote)?;
Ok(ExitCode::SUCCESS)
}
CommandKind::Prune => {
notes.prune()?;
Ok(ExitCode::SUCCESS)
}
}
}
fn review_channel_from_cli(input: &str) -> Result<ReviewChannel, AppError> {
let candidate = ReviewChannelCandidate::new(input)?;
match check_ref_format(candidate.notes_ref_name()) {
Ok(()) => Ok(candidate.into_channel_after_git_check_ref_format()),
Err(CheckRefFormatError::Rejected { ref_name, details }) => Err(candidate
.channel_error(format!(
"`git check-ref-format` rejected {ref_name:?}: {details}"
))
.into()),
Err(CheckRefFormatError::Io(error)) => Err(AppError::Io(error)),
}
}