(&self, ctx: CommandContext)
| 40 | #[async_trait::async_trait] |
| 41 | impl CommandHandler for HistoryHandler { |
| 42 | async fn run(&self, ctx: CommandContext) -> CommandResult { |
| 43 | let file = match ctx.args.get("file").and_then(|v| v.as_str()) { |
| 44 | Some(f) => f.to_string(), |
| 45 | None => { |
| 46 | return CommandResult::Error { |
| 47 | code: "MISSING_ARG".into(), |
| 48 | message: "Missing required argument: file".into(), |
| 49 | retryable: false, |
| 50 | exit_code: Some(1), |
| 51 | cta: None, |
| 52 | }; |
| 53 | } |
| 54 | }; |
| 55 | |
| 56 | let limit = ctx |
| 57 | .options |
| 58 | .get("limit") |
| 59 | .and_then(|v| v.as_i64()) |
| 60 | .unwrap_or(20); |
| 61 | |
| 62 | let (mut engine, _repo_path) = match engine_from_options(&ctx.options) { |
| 63 | Ok(v) => v, |
| 64 | Err(e) => return e, |
| 65 | }; |
| 66 | |
| 67 | // Load commits and diff_files tables |
| 68 | if let Err(e) = engine.load_git_tables(&["commits", "diff_files"]) { |
| 69 | return CommandResult::Error { |
| 70 | code: "LOAD_ERROR".into(), |
| 71 | message: format!("Failed to load git tables: {e}"), |
| 72 | retryable: false, |
| 73 | exit_code: Some(1), |
| 74 | cta: None, |
| 75 | }; |
| 76 | } |
| 77 | |
| 78 | let sql = format!( |
| 79 | "SELECT c.short_id, c.author_name, c.authored_at, c.summary, \ |
| 80 | df.status, df.insertions, df.deletions \ |
| 81 | FROM diff_files df \ |
| 82 | JOIN commits c ON df.commit_id = c.id \ |
| 83 | WHERE df.path LIKE '%{file}%' \ |
| 84 | ORDER BY c.authored_at DESC \ |
| 85 | LIMIT {limit}" |
| 86 | ); |
| 87 | |
| 88 | match engine.query(&sql) { |
| 89 | Ok(commits) => CommandResult::Ok { |
| 90 | data: json!({ |
| 91 | "file_pattern": file, |
| 92 | "total": commits.len(), |
| 93 | "commits": Value::Array(commits), |
| 94 | }), |
| 95 | cta: None, |
| 96 | }, |
| 97 | Err(e) => CommandResult::Error { |
| 98 | code: "QUERY_ERROR".into(), |
| 99 | message: format!("History query failed: {e}"), |
nothing calls this directly
no test coverage detected