Skip to main content

void_core/ops/repair/
mod.rs

1//! Repository repair operations.
2//!
3//! Provides tools to recover from repository corruption by:
4//!
5//! ## Repair Modes
6//!
7//! ### Truncate Mode (`RepairMode::Truncate`)
8//! The safe, recommended option. Moves HEAD and branch refs to the recovery
9//! boundary, discarding broken commits. All healthy history is preserved exactly.
10//!
11//! ### Rewrite History Mode (`RepairMode::RewriteHistory`)
12//! Advanced option that rewrites commits from recovery boundary to HEAD,
13//! skipping broken ones. Preserves healthy commits that came after broken ones,
14//! but invalidates all commit signatures.
15//!
16//! ## Usage
17//!
18//! ```ignore
19//! use void_core::ops::repair::{repair, RepairOptions, RepairMode};
20//!
21//! let opts = RepairOptions {
22//!     void_dir: PathBuf::from(".void"),
23//!     key: encryption_key,
24//!     mode: RepairMode::Truncate,
25//!     dry_run: true,  // Preview first
26//!     checkout: false,
27//! };
28//!
29//! let result = repair(opts)?;
30//! println!("Would preserve {} commits, discard {}",
31//!     result.commits_preserved, result.commits_discarded);
32//! ```
33
34mod rewrite;
35mod truncate;
36mod types;
37
38pub use rewrite::{preview_rewrite, rewrite_skipping_broken};
39pub use truncate::{preview_truncate, truncate_to_boundary};
40pub use types::{BranchUpdate, RepairMode, RepairOptions, RepairPreview, RepairResult};
41
42use crate::Result;
43
44/// Repair a corrupted repository.
45///
46/// Dispatches to the appropriate repair function based on the mode specified
47/// in options.
48///
49/// # Arguments
50/// * `opts` - Repair options including mode, dry_run flag, etc.
51///
52/// # Returns
53/// `RepairResult` with details of what was done (or would be done if dry_run).
54///
55/// # Errors
56/// - `VoidError::NotInitialized` if repository doesn't exist
57/// - `VoidError::Integrity` if no recovery boundary or all commits broken
58/// - `VoidError::Integrity` if repository is already healthy
59///
60/// # Example
61///
62/// ```ignore
63/// // First, preview what would happen
64/// let preview_opts = RepairOptions {
65///     void_dir: void_dir.clone(),
66///     key,
67///     mode: RepairMode::Truncate,
68///     dry_run: true,
69///     checkout: false,
70/// };
71/// let preview = repair(preview_opts)?;
72/// println!("Would discard {} commits", preview.commits_discarded);
73///
74/// // Then apply if satisfied
75/// let repair_opts = RepairOptions {
76///     dry_run: false,
77///     ..preview_opts
78/// };
79/// let result = repair(repair_opts)?;
80/// ```
81pub fn repair(opts: RepairOptions) -> Result<RepairResult> {
82    match &opts.mode {
83        RepairMode::Truncate | RepairMode::TruncateBestEffort => truncate_to_boundary(&opts),
84        RepairMode::RewriteHistory { .. } => rewrite_skipping_broken(&opts),
85    }
86}
87
88/// Preview what a repair operation would do.
89///
90/// This is a convenience wrapper that sets dry_run=true and returns a preview.
91pub fn preview_repair(opts: &RepairOptions) -> Result<RepairPreview> {
92    match &opts.mode {
93        RepairMode::Truncate | RepairMode::TruncateBestEffort => preview_truncate(opts),
94        RepairMode::RewriteHistory { .. } => preview_rewrite(opts),
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::crypto::KeyVault;
102
103    #[test]
104    fn repair_mode_default_is_truncate() {
105        assert_eq!(RepairMode::default(), RepairMode::Truncate);
106    }
107
108    #[test]
109    fn repair_options_clone() {
110        let opts = RepairOptions {
111            ctx: crate::VoidContext::headless(
112                "/test/.void",
113                std::sync::Arc::new(KeyVault::new([0u8; 32]).expect("key derivation should not fail")),
114                0,
115            ).unwrap(),
116            mode: RepairMode::Truncate,
117            dry_run: true,
118            checkout: false,
119        };
120        let cloned = opts.clone();
121        assert_eq!(cloned.ctx.paths.void_dir, opts.ctx.paths.void_dir);
122        assert_eq!(cloned.dry_run, opts.dry_run);
123    }
124}