capsula_git_context/
lib.rs1mod config;
2mod error;
3
4use crate::error::GitContextError;
5
6use crate::config::GitContextFactory;
7use capsula_core::captured::Captured;
8use capsula_core::context::{Context, ContextFactory, RuntimeParams};
9use capsula_core::error::CoreResult;
10use git2::Repository;
11use serde_json::json;
12use std::path::PathBuf;
13
14pub const KEY: &str = "git";
15
16#[derive(Debug)]
17pub struct GitContext {
18 pub name: String,
19 pub working_dir: PathBuf,
20 pub allow_dirty: bool,
21}
22
23#[derive(Debug)]
24pub struct GitCaptured {
25 pub name: String,
26 pub working_dir: PathBuf,
27 pub sha: String, pub is_dirty: bool,
29 pub abort_on_dirty: bool,
30}
31
32impl Captured for GitCaptured {
33 fn to_json(&self) -> serde_json::Value {
34 json!({
35 "type": KEY.to_string(),
36 "name": self.name,
37 "working_dir": self.working_dir.to_string_lossy(),
38 "sha": self.sha,
39 "is_dirty": self.is_dirty,
40 "abort_on_dirty": self.abort_on_dirty
41 })
42 }
43
44 fn abort_requested(&self) -> bool {
45 self.is_dirty && self.abort_on_dirty
46 }
47}
48
49impl Context for GitContext {
50 type Output = GitCaptured;
51
52 fn run(&self, _params: &RuntimeParams) -> CoreResult<Self::Output> {
53 let repo_path = if self.working_dir.as_os_str().is_empty() {
54 std::env::current_dir()?
55 } else {
56 self.working_dir.clone()
57 };
58
59 let repo = Repository::discover(&repo_path).map_err(|e| {
60 if e.code() == git2::ErrorCode::NotFound {
61 GitContextError::NotARepository
62 } else {
63 GitContextError::GitOperation(e)
64 }
65 })?;
66
67 let head = repo.head().map_err(GitContextError::from)?;
68 let oid = head.target().ok_or_else(|| GitContextError::HeadNotFound {
69 message: "HEAD does not point to a valid commit".to_string(),
70 })?;
71
72 let statuses = repo.statuses(None).map_err(GitContextError::from)?;
74 let is_dirty = !statuses.is_empty();
75
76 if is_dirty && !self.allow_dirty {
79 eprintln!("Warning: Repository has uncommitted changes. Run will be aborted after context capture.");
80 }
81
82 Ok(GitCaptured {
83 name: self.name.clone(),
84 working_dir: repo_path,
85 sha: oid.to_string(),
86 is_dirty,
87 abort_on_dirty: !self.allow_dirty,
88 })
89 }
90}
91
92pub fn create_factory() -> Box<dyn ContextFactory> {
94 Box::new(GitContextFactory)
95}