use incurs::command::{CommandContext, CommandDef, CommandHandler, Example};
use incurs::output::CommandResult;
use serde_json::{json, Value};
use super::engine_from_options;
#[derive(incurs::Args, serde::Deserialize)]
#[allow(dead_code)]
struct HistoryArgs {
file: String,
}
#[derive(incurs::Options, serde::Deserialize)]
#[allow(dead_code)]
struct HistoryOptions {
#[incur(alias = "r", default = ".")]
repo: String,
#[incur(alias = "d")]
data_dir: Option<String>,
#[incur(alias = "n", default = "20")]
limit: i64,
}
struct HistoryHandler;
#[async_trait::async_trait]
impl CommandHandler for HistoryHandler {
async fn run(&self, ctx: CommandContext) -> CommandResult {
let file = match ctx.args.get("file").and_then(|v| v.as_str()) {
Some(f) => f.to_string(),
None => {
return CommandResult::Error {
code: "MISSING_ARG".into(),
message: "Missing required argument: file".into(),
retryable: false,
exit_code: Some(1),
cta: None,
};
}
};
let limit = ctx
.options
.get("limit")
.and_then(|v| v.as_i64())
.unwrap_or(20);
let (mut engine, _repo_path) = match engine_from_options(&ctx.options) {
Ok(v) => v,
Err(e) => return e,
};
if let Err(e) = engine.load_git_tables(&["commits", "diff_files"]) {
return CommandResult::Error {
code: "LOAD_ERROR".into(),
message: format!("Failed to load git tables: {e}"),
retryable: false,
exit_code: Some(1),
cta: None,
};
}
let sql = format!(
"SELECT c.short_id, c.author_name, c.authored_at, c.summary, \
df.status, df.insertions, df.deletions \
FROM diff_files df \
JOIN commits c ON df.commit_id = c.id \
WHERE df.path LIKE '%{file}%' \
ORDER BY c.authored_at DESC \
LIMIT {limit}"
);
match engine.query(&sql) {
Ok(commits) => CommandResult::Ok {
data: json!({
"file_pattern": file,
"total": commits.len(),
"commits": Value::Array(commits),
}),
cta: None,
},
Err(e) => CommandResult::Error {
code: "QUERY_ERROR".into(),
message: format!("History query failed: {e}"),
retryable: false,
exit_code: Some(1),
cta: None,
},
}
}
}
pub fn build() -> CommandDef {
CommandDef::build("history", HistoryHandler)
.description("Show Git commit history for a specific file")
.args::<HistoryArgs>()
.options::<HistoryOptions>()
.examples(vec![
Example {
command: "src/engine.rs --json".to_string(),
description: Some("Show commit history for engine.rs".to_string()),
},
Example {
command: "Cargo.toml --limit 5 --json".to_string(),
description: Some("Show last 5 commits touching Cargo.toml".to_string()),
},
])
.done()
}