Skip to main content

ralph_workflow/git_helpers/
mod.rs

1//! Git Helper Functions
2//!
3//! Provides git hooks management, a git wrapper for blocking commits during the
4//! agent phase, and basic repository utilities.
5//!
6//! All git operations use libgit2 directly - no git CLI required.
7//!
8//! # Module Structure
9//!
10//! - `hooks` - Git hooks installation and removal
11//! - [`identity`] - Git identity resolution with comprehensive fallback chain
12//! - `repo` - Basic git repository operations (add, commit, snapshot)
13//! - `start_commit` - Starting commit tracking for incremental diffs
14//! - `review_baseline` - Per-review-cycle baseline tracking
15//! - `wrapper` - Agent phase git wrapper for safe concurrent execution
16//! - [`branch`] - Branch detection and default branch resolution
17//! - `rebase` - Rebase operations with fault tolerance
18
19#![deny(unsafe_code)]
20
21use std::io;
22
23/// Convert git2 errors to std::io errors for consistent error handling.
24#[cfg(any(test, feature = "test-utils"))]
25pub fn git2_to_io_error(err: &git2::Error) -> io::Error {
26    git2_to_io_error_impl(err)
27}
28
29#[cfg(not(any(test, feature = "test-utils")))]
30pub(crate) fn git2_to_io_error(err: &git2::Error) -> io::Error {
31    git2_to_io_error_impl(err)
32}
33
34fn git2_to_io_error_impl(err: &git2::Error) -> io::Error {
35    // Fall back to mapping git2 error codes to a best-effort io::ErrorKind.
36    let kind = match err.code() {
37        git2::ErrorCode::NotFound => io::ErrorKind::NotFound,
38        git2::ErrorCode::Exists => io::ErrorKind::AlreadyExists,
39        git2::ErrorCode::Auth => io::ErrorKind::PermissionDenied,
40        git2::ErrorCode::Certificate => io::ErrorKind::PermissionDenied,
41        git2::ErrorCode::Invalid => io::ErrorKind::InvalidInput,
42        git2::ErrorCode::Eof => io::ErrorKind::UnexpectedEof,
43        git2::ErrorCode::UnbornBranch => io::ErrorKind::NotFound,
44        _ => io::ErrorKind::Other,
45    };
46
47    io::Error::new(kind, err.to_string())
48}
49
50pub mod branch;
51#[cfg(any(test, feature = "test-utils"))]
52pub mod hooks;
53#[cfg(not(any(test, feature = "test-utils")))]
54mod hooks;
55pub mod identity;
56mod rebase;
57
58#[cfg(any(test, feature = "test-utils"))]
59pub mod rebase_checkpoint;
60
61#[cfg(any(test, feature = "test-utils"))]
62pub mod rebase_state_machine;
63
64#[cfg(any(test, feature = "test-utils"))]
65pub mod repo;
66#[cfg(not(any(test, feature = "test-utils")))]
67mod repo;
68mod review_baseline;
69mod start_commit;
70mod wrapper;
71
72#[cfg(any(test, feature = "test-utils"))]
73pub use branch::get_default_branch_at;
74pub use branch::{get_default_branch, is_main_or_master_branch};
75pub use hooks::uninstall_hooks;
76#[cfg(any(test, feature = "test-utils"))]
77pub use hooks::{file_contains_marker_with_workspace, verify_hook_integrity_with_workspace};
78pub use rebase::{
79    abort_rebase, continue_rebase, get_conflict_markers_for_file, get_conflicted_files,
80    rebase_in_progress, rebase_onto, RebaseResult,
81};
82
83// Types that are part of the public API but not used in binary
84#[cfg(any(test, feature = "test-utils"))]
85pub use rebase::{CleanupResult, ConcurrentOperation};
86
87#[cfg(any(test, feature = "test-utils"))]
88pub use rebase::{
89    attempt_automatic_recovery, cleanup_stale_rebase_state, detect_concurrent_git_operations,
90    is_dirty_tree_cli, rebase_in_progress_cli, validate_rebase_preconditions,
91    verify_rebase_completed,
92};
93
94pub use rebase::RebaseErrorKind;
95
96#[cfg(any(test, feature = "test-utils"))]
97pub use rebase_checkpoint::RebasePhase;
98
99#[cfg(any(test, feature = "test-utils"))]
100pub use rebase_state_machine::{RebaseLock, RebaseStateMachine};
101pub use repo::{
102    get_git_diff_for_review_with_workspace, get_git_diff_from_start,
103    get_git_diff_from_start_with_workspace, get_repo_root, git_add_all, git_add_all_in_repo,
104    git_commit, git_commit_in_repo, git_diff, git_diff_from, git_snapshot, require_git_repo,
105    CommitResultFallback, DiffReviewContent, DiffTruncationLevel,
106};
107#[cfg(any(test, feature = "test-utils"))]
108pub use review_baseline::load_review_baseline_with_workspace;
109pub use review_baseline::update_review_baseline_with_workspace;
110pub use review_baseline::{
111    get_baseline_summary, get_review_baseline_info, load_review_baseline, update_review_baseline,
112    ReviewBaseline,
113};
114#[cfg(any(test, feature = "test-utils"))]
115pub use start_commit::load_start_point_with_workspace;
116pub use start_commit::{
117    get_current_head_oid, get_start_commit_summary, load_start_point, reset_start_commit,
118    save_start_commit, save_start_commit_with_workspace, StartPoint,
119};
120pub use wrapper::{
121    cleanup_agent_phase_silent, cleanup_orphaned_marker, disable_git_wrapper, end_agent_phase,
122    start_agent_phase, GitHelpers,
123};
124
125// Workspace-aware variants (used by tests and by code paths that must operate
126// without requiring a real git repository).
127pub use wrapper::{
128    cleanup_orphaned_marker_with_workspace, create_marker_with_workspace,
129    marker_exists_with_workspace, remove_marker_with_workspace,
130};
131
132// Re-export checkpoint and recovery action for tests only
133#[cfg(any(test, feature = "test-utils"))]
134pub use rebase_checkpoint::RebaseCheckpoint;
135
136#[cfg(any(test, feature = "test-utils"))]
137pub use rebase_state_machine::RecoveryAction;
138
139#[cfg(test)]
140mod tests;