Skip to main content

git_vet/
cli.rs

1use std::path::PathBuf;
2use std::process::ExitCode;
3use std::str::FromStr;
4
5use clap::{Parser, Subcommand};
6
7use crate::channel::{DEFAULT_REVIEW_CHANNEL, ReviewChannel};
8use crate::commands::{Gate, StatusMode, diff_path, mark_paths, status};
9use crate::error::AppError;
10use crate::git::Git;
11use crate::notes::{GixNotesStore, NotesStore};
12
13#[derive(Parser, Debug)]
14#[command(
15    name = "git-vet",
16    version,
17    about = "Track human review state for Git-tracked file contents"
18)]
19pub struct Cli {
20    /// Review channel/pipeline to read or write.
21    #[arg(long, global = true, default_value = DEFAULT_REVIEW_CHANNEL)]
22    channel: String,
23    #[command(subcommand)]
24    command: CommandKind,
25}
26
27#[derive(Subcommand, Debug)]
28enum CommandKind {
29    /// Sign off the current HEAD content of tracked files.
30    Mark {
31        #[arg(required = true)]
32        paths: Vec<PathBuf>,
33    },
34    /// Show review state for tracked files.
35    Status {
36        /// Emit stable machine-readable JSON.
37        #[arg(long)]
38        json: bool,
39        /// Exit 1 when any in-scope tracked file is unreviewed.
40        #[arg(long)]
41        check: bool,
42    },
43    /// Show the diff that still needs review for a tracked file.
44    Diff { path: PathBuf },
45    /// Prune notes for objects that are no longer present.
46    Prune,
47}
48
49pub fn run_cli() -> Result<ExitCode, AppError> {
50    let cli = Cli::parse();
51    let channel = ReviewChannel::from_str(&cli.channel)?;
52    let git = Git::discover()?;
53    let notes = GixNotesStore::new(&git, channel.notes_ref().clone());
54
55    match cli.command {
56        CommandKind::Mark { paths } => {
57            mark_paths(&git, &notes, &paths)?;
58            Ok(ExitCode::SUCCESS)
59        }
60        CommandKind::Status { json, check } => {
61            match status(&git, &notes, &channel, StatusMode { json, check })? {
62                Gate::Open => Ok(ExitCode::SUCCESS),
63                Gate::Closed => Ok(ExitCode::from(1)),
64            }
65        }
66        CommandKind::Diff { path } => {
67            diff_path(&git, &notes, &path)?;
68            Ok(ExitCode::SUCCESS)
69        }
70        CommandKind::Prune => {
71            notes.prune()?;
72            Ok(ExitCode::SUCCESS)
73        }
74    }
75}