pocket_cli/vcs/
diff.rs

1//! Diff functionality for Pocket VCS
2//!
3//! Handles the calculation and representation of differences between files.
4
5use std::path::{Path, PathBuf};
6use anyhow::Result;
7
8use crate::vcs::{ObjectId, ObjectStore};
9
10/// Options for controlling diff behavior
11#[derive(Debug, Clone)]
12pub struct DiffOptions {
13    pub context_lines: usize,
14    pub ignore_whitespace: bool,
15    pub ignore_case: bool,
16}
17
18impl Default for DiffOptions {
19    fn default() -> Self {
20        Self {
21            context_lines: 3,
22            ignore_whitespace: false,
23            ignore_case: false,
24        }
25    }
26}
27
28/// A single change in a diff
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum DiffChange {
31    /// Lines added (new content)
32    Added {
33        start: usize,
34        lines: Vec<String>,
35    },
36    /// Lines removed (old content)
37    Removed {
38        start: usize,
39        lines: Vec<String>,
40    },
41    /// Lines changed (old content to new content)
42    Changed {
43        old_start: usize,
44        old_lines: Vec<String>,
45        new_start: usize,
46        new_lines: Vec<String>,
47    },
48}
49
50/// A hunk of changes in a diff
51#[derive(Debug, Clone)]
52pub struct DiffHunk {
53    pub old_start: usize,
54    pub old_count: usize,
55    pub new_start: usize,
56    pub new_count: usize,
57    pub changes: Vec<DiffChange>,
58}
59
60/// The result of a diff operation
61#[derive(Debug, Clone)]
62pub struct DiffResult {
63    pub old_path: PathBuf,
64    pub new_path: PathBuf,
65    pub hunks: Vec<DiffHunk>,
66    pub is_binary: bool,
67}
68
69/// Diff calculator
70pub struct Diff {
71    pub options: DiffOptions,
72}
73
74impl Diff {
75    /// Create a new diff calculator with default options
76    pub fn new() -> Self {
77        Self {
78            options: DiffOptions::default(),
79        }
80    }
81    
82    /// Create a new diff calculator with custom options
83    pub fn with_options(options: DiffOptions) -> Self {
84        Self { options }
85    }
86    
87    /// Calculate the diff between two files
88    pub fn diff_files(&self, old_path: &Path, new_path: &Path) -> Result<DiffResult> {
89        // Read file contents
90        let old_content = std::fs::read_to_string(old_path)?;
91        let new_content = std::fs::read_to_string(new_path)?;
92        
93        // Calculate diff
94        self.diff_content(
95            old_path.to_path_buf(),
96            &old_content,
97            new_path.to_path_buf(),
98            &new_content,
99        )
100    }
101    
102    /// Calculate the diff between two objects in the object store
103    pub fn diff_objects(
104        &self,
105        object_store: &ObjectStore,
106        old_id: &ObjectId,
107        old_path: PathBuf,
108        new_id: &ObjectId,
109        new_path: PathBuf,
110    ) -> Result<DiffResult> {
111        // Get object contents
112        let old_content = String::from_utf8(object_store.get_object(old_id)?)?;
113        let new_content = String::from_utf8(object_store.get_object(new_id)?)?;
114        
115        // Calculate diff
116        self.diff_content(old_path, &old_content, new_path, &new_content)
117    }
118    
119    /// Calculate the diff between two content strings
120    pub fn diff_content(
121        &self,
122        old_path: PathBuf,
123        old_content: &str,
124        new_path: PathBuf,
125        new_content: &str,
126    ) -> Result<DiffResult> {
127        // Split content into lines
128        let old_lines: Vec<&str> = old_content.lines().collect();
129        let new_lines: Vec<&str> = new_content.lines().collect();
130        
131        // Check if files are binary
132        let is_binary = old_content.contains('\0') || new_content.contains('\0');
133        if is_binary {
134            return Ok(DiffResult {
135                old_path,
136                new_path,
137                hunks: vec![],
138                is_binary: true,
139            });
140        }
141        
142        // Calculate diff using the Myers diff algorithm
143        // This is a simplified implementation - a real one would use a more
144        // efficient algorithm like the one in the `diff` crate
145        let hunks = self.myers_diff(&old_lines, &new_lines)?;
146        
147        Ok(DiffResult {
148            old_path,
149            new_path,
150            hunks,
151            is_binary: false,
152        })
153    }
154    
155    /// Implement the Myers diff algorithm
156    fn myers_diff(&self, old_lines: &[&str], new_lines: &[&str]) -> Result<Vec<DiffHunk>> {
157        // This is a placeholder for the actual implementation
158        // A real implementation would use a proper diff algorithm
159        
160        // For now, just return a simple diff that shows all lines as changed
161        let mut hunks = Vec::new();
162        
163        if !old_lines.is_empty() || !new_lines.is_empty() {
164            let changes = vec![DiffChange::Changed {
165                old_start: 1,
166                old_lines: old_lines.iter().map(|s| s.to_string()).collect(),
167                new_start: 1,
168                new_lines: new_lines.iter().map(|s| s.to_string()).collect(),
169            }];
170            
171            hunks.push(DiffHunk {
172                old_start: 1,
173                old_count: old_lines.len(),
174                new_start: 1,
175                new_count: new_lines.len(),
176                changes,
177            });
178        }
179        
180        Ok(hunks)
181    }
182    
183    // Additional methods would be implemented here:
184    // - format_diff: Format a diff result as a string
185    // - apply_diff: Apply a diff to a file
186    // - etc.
187}