soma_core/git_integration/
mod.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4use std::fs;
5use std::time::{SystemTime, UNIX_EPOCH};
6use crate::edit_control::ModifiableEdit;
7use crate::classification::{ClassifiedEdit, EditCategory};
8
9
10/// Git Integration System for SOMA-CORE
11/// Provides comprehensive Git workflow management with intelligent branching,
12/// automated commits, conflict resolution, and backup strategies
13#[derive(Debug, Clone)]
14pub struct GitIntegrationSystem {
15    /// Repository root path
16    pub repository_path: PathBuf,
17    /// Current Git configuration
18    pub config: GitConfig,
19    /// Branch management system
20    pub branch_manager: BranchManager,
21    /// Commit management system
22    pub commit_manager: CommitManager,
23    /// Conflict resolution system
24    pub conflict_resolver: ConflictResolver,
25    /// Backup management system
26    pub backup_manager: BackupManager,
27    /// Current session state
28    pub session_state: GitSessionState,
29}
30
31/// Git configuration for SOMA-CORE integration
32#[derive(Debug, Clone)]
33pub struct GitConfig {
34    /// Automatic commit on successful edits
35    pub auto_commit: bool,
36    /// Create session branches automatically
37    pub session_branches: bool,
38    /// Backup frequency in minutes
39    pub backup_frequency_minutes: u32,
40    /// Maximum backup retention days
41    pub max_backup_days: u32,
42    /// Integration with CI/CD pipelines
43    pub cicd_integration: bool,
44    /// Conflict resolution strategy
45    pub conflict_strategy: ConflictStrategy,
46}
47
48/// Git session state tracking
49#[derive(Debug, Clone)]
50pub struct GitSessionState {
51    /// Unique session identifier
52    pub session_id: String,
53    /// Session start timestamp
54    pub start_time: SystemTime,
55    /// Current branch name
56    pub current_branch: String,
57    /// Original branch name
58    pub original_branch: String,
59    /// Session edits applied
60    pub edits_applied: Vec<String>,
61    /// Session commit history
62    pub commit_history: Vec<GitCommit>,
63    /// Session backup points
64    pub backup_points: Vec<BackupPoint>,
65}
66
67/// Branch management system
68#[derive(Debug, Clone)]
69pub struct BranchManager {
70    /// Current repository path
71    pub repo_path: PathBuf,
72    /// Branch naming strategy
73    pub naming_strategy: BranchNamingStrategy,
74    /// Automatic cleanup of session branches
75    pub auto_cleanup: bool,
76    /// Maximum branch lifetime in days
77    pub max_branch_lifetime_days: u32,
78}
79
80/// Commit management system
81#[derive(Debug, Clone)]
82pub struct CommitManager {
83    /// Repository path
84    pub repo_path: PathBuf,
85    /// Commit message templates
86    pub message_templates: HashMap<String, String>,
87    /// Automatic staging of changes
88    pub auto_stage: bool,
89    /// Sign commits with GPG
90    pub sign_commits: bool,
91}
92
93/// Conflict resolution system
94#[derive(Debug, Clone)]
95pub struct ConflictResolver {
96    /// Repository path
97    pub repo_path: PathBuf,
98    /// Resolution strategies
99    pub strategies: Vec<ConflictStrategy>,
100    /// Integration with classification system
101    pub use_classification: bool,
102    /// Cognitive operator integration
103    pub cognitive_resolution: bool,
104}
105
106/// Backup management system
107#[derive(Debug, Clone)]
108pub struct BackupManager {
109    /// Repository path
110    pub repo_path: PathBuf,
111    /// Backup storage directory
112    pub backup_dir: PathBuf,
113    /// Compression enabled
114    pub compress_backups: bool,
115    /// Incremental backup strategy
116    pub incremental: bool,
117}
118
119/// Git commit representation
120#[derive(Debug, Clone)]
121pub struct GitCommit {
122    /// Commit hash
123    pub hash: String,
124    /// Commit message
125    pub message: String,
126    /// Commit timestamp
127    pub timestamp: SystemTime,
128    /// Files changed
129    pub files_changed: Vec<String>,
130    /// Associated SOMA edit IDs
131    pub soma_edit_ids: Vec<String>,
132}
133
134/// Backup point for rollback capabilities
135#[derive(Debug, Clone)]
136pub struct BackupPoint {
137    /// Backup identifier
138    pub id: String,
139    /// Backup timestamp
140    pub timestamp: SystemTime,
141    /// Repository state hash
142    pub state_hash: String,
143    /// Backup file path
144    pub backup_path: PathBuf,
145    /// Description of backup point
146    pub description: String,
147}
148
149/// Branch naming strategies
150#[derive(Debug, Clone)]
151pub enum BranchNamingStrategy {
152    /// soma-session-{timestamp}
153    SessionTimestamp,
154    /// soma-session-{session_id}
155    SessionId,
156    /// soma-{user}-{timestamp}
157    UserTimestamp,
158    /// Custom naming pattern
159    Custom(String),
160}
161
162/// Conflict resolution strategies
163#[derive(Debug, Clone)]
164pub enum ConflictStrategy {
165    /// Prefer local changes
166    PreferLocal,
167    /// Prefer remote changes
168    PreferRemote,
169    /// Interactive resolution
170    Interactive,
171    /// Classification-based resolution
172    ClassificationBased,
173    /// Cognitive operator resolution
174    CognitiveResolution,
175}
176
177/// Git operation results
178#[derive(Debug, Clone)]
179pub enum GitOperationResult {
180    /// Operation succeeded
181    Success(String),
182    /// Operation failed with error
183    Error(String),
184    /// Operation requires user intervention
185    RequiresIntervention(String),
186    /// Conflict detected
187    Conflict(ConflictInfo),
188}
189
190/// Conflict information
191#[derive(Debug, Clone)]
192pub struct ConflictInfo {
193    /// Conflicted files
194    pub files: Vec<String>,
195    /// Conflict descriptions
196    pub descriptions: Vec<String>,
197    /// Suggested resolution strategy
198    pub suggested_strategy: ConflictStrategy,
199    /// Classification results for conflicted edits
200    pub classification_results: Vec<ClassifiedEdit>,
201}
202
203impl Default for GitConfig {
204    fn default() -> Self {
205        GitConfig {
206            auto_commit: true,
207            session_branches: true,
208            backup_frequency_minutes: 30,
209            max_backup_days: 7,
210            cicd_integration: false,
211            conflict_strategy: ConflictStrategy::Interactive,
212        }
213    }
214}
215
216impl Default for BranchNamingStrategy {
217    fn default() -> Self {
218        BranchNamingStrategy::SessionTimestamp
219    }
220}
221
222impl GitIntegrationSystem {
223    /// Create a new Git integration system
224    pub fn new(repository_path: PathBuf) -> Result<Self, String> {
225        // Validate repository
226        if !Self::is_git_repository(&repository_path) {
227            return Err(format!("Path {:?} is not a Git repository", repository_path));
228        }
229
230        let config = GitConfig::default();
231        let branch_manager = BranchManager::new(repository_path.clone())?;
232        let commit_manager = CommitManager::new(repository_path.clone())?;
233        let conflict_resolver = ConflictResolver::new(repository_path.clone())?;
234        let backup_manager = BackupManager::new(repository_path.clone())?;
235        
236        let session_id = Self::generate_session_id();
237        let current_branch = Self::get_current_branch(&repository_path)?;
238        
239        let session_state = GitSessionState {
240            session_id,
241            start_time: SystemTime::now(),
242            current_branch: current_branch.clone(),
243            original_branch: current_branch,
244            edits_applied: Vec::new(),
245            commit_history: Vec::new(),
246            backup_points: Vec::new(),
247        };
248
249        Ok(GitIntegrationSystem {
250            repository_path,
251            config,
252            branch_manager,
253            commit_manager,
254            conflict_resolver,
255            backup_manager,
256            session_state,
257        })
258    }
259
260    /// Initialize a new SOMA editing session with Git integration
261    pub fn start_session(&mut self, session_name: Option<String>) -> Result<String, String> {
262        // Create session branch if enabled
263        if self.config.session_branches {
264            let branch_name = self.branch_manager.create_session_branch(
265                session_name.clone()
266            )?;
267            self.session_state.current_branch = branch_name.clone();
268            
269            // Switch to session branch
270            self.switch_branch(&branch_name)?;
271        }
272
273        // Create initial backup point
274        let backup_point = self.backup_manager.create_backup_point(
275            "Session start".to_string()
276        )?;
277        self.session_state.backup_points.push(backup_point);
278
279        // Initialize commit manager for session
280        self.commit_manager.initialize_session(&self.session_state.session_id)?;
281
282        Ok(format!("Started SOMA Git session: {}", self.session_state.session_id))
283    }
284
285    /// Apply edits with Git integration
286    pub fn apply_edits_with_git(
287        &mut self, 
288        edits: Vec<ModifiableEdit>,
289        classifications: Vec<ClassifiedEdit>
290    ) -> Result<Vec<GitOperationResult>, String> {
291        let mut results = Vec::new();
292
293        // Group edits by risk level from classification
294        let mut critical_edits = Vec::new();
295        let mut safe_edits = Vec::new();
296        let mut experimental_edits = Vec::new();
297
298        for (edit, classification) in edits.iter().zip(classifications.iter()) {
299            match &classification.category {
300                EditCategory::Critical { .. } => {
301                    critical_edits.push((edit, classification));
302                }
303                EditCategory::Safe { .. } => {
304                    safe_edits.push((edit, classification));
305                }
306                EditCategory::Experimental { .. } => {
307                    experimental_edits.push((edit, classification));
308                }
309                EditCategory::Cosmetic { .. } => {
310                    safe_edits.push((edit, classification));
311                }
312            }
313        }
314
315        // Apply safe edits first
316        for (edit, classification) in safe_edits {
317            let result = self.apply_single_edit_with_git(edit, classification)?;
318            results.push(result);
319        }
320
321        // Apply experimental edits with additional backup
322        if !experimental_edits.is_empty() {
323            let backup_point = self.backup_manager.create_backup_point(
324                "Before experimental edits".to_string()
325            )?;
326            self.session_state.backup_points.push(backup_point);
327
328            for (edit, classification) in experimental_edits {
329                let result = self.apply_single_edit_with_git(edit, classification)?;
330                results.push(result);
331            }
332        }
333
334        // Apply critical edits with maximum safeguards
335        if !critical_edits.is_empty() {
336            let backup_point = self.backup_manager.create_backup_point(
337                "Before critical edits".to_string()
338            )?;
339            self.session_state.backup_points.push(backup_point);
340
341            for (edit, classification) in critical_edits {
342                let result = self.apply_single_edit_with_git(edit, classification)?;
343                results.push(result.clone());
344                
345                // Immediate commit for critical edits
346                if matches!(result, GitOperationResult::Success(_)) {
347                    self.commit_current_changes(&format!(
348                        "SOMA Critical Edit: {}", 
349                        classification.reasoning
350                    ))?;
351                }
352            }
353        }
354
355        Ok(results)
356    }
357
358    /// Apply a single edit with Git integration
359    fn apply_single_edit_with_git(
360        &mut self,
361        edit: &ModifiableEdit,
362        classification: &ClassifiedEdit
363    ) -> Result<GitOperationResult, String> {
364        // Check for conflicts before applying
365        if let Some(conflicts) = self.check_for_conflicts(&edit.base_edit.file)? {
366            return Ok(GitOperationResult::Conflict(conflicts));
367        }
368
369        // Apply the edit (this would integrate with your existing edit application logic)
370        // For now, we'll simulate the edit application
371        let file_path = &edit.base_edit.file;
372        
373        // Record the edit in session state
374        self.session_state.edits_applied.push(format!("{:?}", edit.base_edit));
375
376        // Auto-commit if enabled and edit is not critical
377        if self.config.auto_commit && !matches!(classification.category, EditCategory::Critical { .. }) {
378            let commit_message = self.commit_manager.generate_commit_message(edit, classification);
379            match self.commit_current_changes(&commit_message) {
380                Ok(commit_hash) => {
381                    let commit = GitCommit {
382                        hash: commit_hash.clone(),
383                        message: commit_message,
384                        timestamp: SystemTime::now(),
385                        files_changed: vec![file_path.clone()],
386                        soma_edit_ids: vec![format!("{:?}", edit)],
387                    };
388                    self.session_state.commit_history.push(commit);
389                    Ok(GitOperationResult::Success(commit_hash))
390                }
391                Err(e) => Ok(GitOperationResult::Error(e)),
392            }
393        } else {
394            Ok(GitOperationResult::Success("Edit applied successfully".to_string()))
395        }
396    }
397
398    /// End the current SOMA editing session
399    pub fn end_session(&mut self, merge_to_main: bool) -> Result<String, String> {
400        // Create final backup
401        let final_backup = self.backup_manager.create_backup_point(
402            "Session end".to_string()
403        )?;
404        self.session_state.backup_points.push(final_backup);
405
406        // Commit any pending changes
407        if self.has_uncommitted_changes()? {
408            self.commit_current_changes("SOMA Session final commit")?;
409        }
410
411        let session_summary = format!(
412            "Session {} completed:\n- Edits applied: {}\n- Commits: {}\n- Backups: {}",
413            self.session_state.session_id,
414            self.session_state.edits_applied.len(),
415            self.session_state.commit_history.len(),
416            self.session_state.backup_points.len()
417        );
418
419        // Merge to main branch if requested
420        if merge_to_main && self.config.session_branches {
421            self.merge_session_to_main()?;
422        }
423
424        // Switch back to original branch
425        if self.config.session_branches {
426            self.switch_branch(&self.session_state.original_branch)?;
427        }
428
429        Ok(session_summary)
430    }
431
432    /// Check if path is a Git repository
433    fn is_git_repository(path: &Path) -> bool {
434        path.join(".git").exists()
435    }
436
437    /// Generate unique session ID
438    fn generate_session_id() -> String {
439        let timestamp = SystemTime::now()
440            .duration_since(UNIX_EPOCH)
441            .unwrap()
442            .as_secs();
443        format!("soma-{}", timestamp)
444    }
445
446    /// Get current Git branch
447    fn get_current_branch(repo_path: &Path) -> Result<String, String> {
448        let output = Command::new("git")
449            .arg("branch")
450            .arg("--show-current")
451            .current_dir(repo_path)
452            .output()
453            .map_err(|e| format!("Failed to get current branch: {}", e))?;
454
455        if output.status.success() {
456            let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
457            Ok(branch)
458        } else {
459            let error = String::from_utf8_lossy(&output.stderr);
460            Err(format!("Git error: {}", error))
461        }
462    }
463
464    /// Switch to specified branch
465    fn switch_branch(&self, branch_name: &str) -> Result<(), String> {
466        let output = Command::new("git")
467            .arg("checkout")
468            .arg(branch_name)
469            .current_dir(&self.repository_path)
470            .output()
471            .map_err(|e| format!("Failed to switch branch: {}", e))?;
472
473        if output.status.success() {
474            Ok(())
475        } else {
476            let error = String::from_utf8_lossy(&output.stderr);
477            Err(format!("Failed to switch to branch {}: {}", branch_name, error))
478        }
479    }
480
481    /// Check for conflicts in specified file
482    pub fn check_for_conflicts(&self, file_path: &str) -> Result<Option<ConflictInfo>, String> {
483        // Check if file has merge conflicts
484        let file_content = fs::read_to_string(file_path)
485            .map_err(|e| format!("Failed to read file {}: {}", file_path, e))?;
486
487        if file_content.contains("<<<<<<< HEAD") || 
488           file_content.contains("=======") || 
489           file_content.contains(">>>>>>> ") {
490            
491            let conflict_info = ConflictInfo {
492                files: vec![file_path.to_string()],
493                descriptions: vec!["Merge conflict detected".to_string()],
494                suggested_strategy: ConflictStrategy::Interactive,
495                classification_results: Vec::new(),
496            };
497            
498            Ok(Some(conflict_info))
499        } else {
500            Ok(None)
501        }
502    }
503
504    /// Commit current changes
505    pub fn commit_current_changes(&self, message: &str) -> Result<String, String> {
506        // Stage all changes
507        let stage_output = Command::new("git")
508            .arg("add")
509            .arg(".")
510            .current_dir(&self.repository_path)
511            .output()
512            .map_err(|e| format!("Failed to stage changes: {}", e))?;
513
514        if !stage_output.status.success() {
515            let error = String::from_utf8_lossy(&stage_output.stderr);
516            return Err(format!("Failed to stage changes: {}", error));
517        }
518
519        // Commit changes
520        let commit_output = Command::new("git")
521            .arg("commit")
522            .arg("-m")
523            .arg(message)
524            .current_dir(&self.repository_path)
525            .output()
526            .map_err(|e| format!("Failed to commit: {}", e))?;
527
528        if commit_output.status.success() {
529            // Get commit hash
530            let hash_output = Command::new("git")
531                .arg("rev-parse")
532                .arg("HEAD")
533                .current_dir(&self.repository_path)
534                .output()
535                .map_err(|e| format!("Failed to get commit hash: {}", e))?;
536
537            if hash_output.status.success() {
538                let hash = String::from_utf8_lossy(&hash_output.stdout).trim().to_string();
539                Ok(hash)
540            } else {
541                Ok("unknown".to_string())
542            }
543        } else {
544            let error = String::from_utf8_lossy(&commit_output.stderr);
545            Err(format!("Failed to commit: {}", error))
546        }
547    }
548
549    /// Check if there are uncommitted changes
550    fn has_uncommitted_changes(&self) -> Result<bool, String> {
551        let output = Command::new("git")
552            .arg("status")
553            .arg("--porcelain")
554            .current_dir(&self.repository_path)
555            .output()
556            .map_err(|e| format!("Failed to check git status: {}", e))?;
557
558        if output.status.success() {
559            let status = String::from_utf8_lossy(&output.stdout);
560            Ok(!status.trim().is_empty())
561        } else {
562            let error = String::from_utf8_lossy(&output.stderr);
563            Err(format!("Git status error: {}", error))
564        }
565    }
566
567    /// Merge session branch to main
568    pub fn merge_session_to_main(&self) -> Result<(), String> {
569        // Switch to main branch
570        self.switch_branch("main")?;
571
572        // Merge session branch
573        let merge_output = Command::new("git")
574            .arg("merge")
575            .arg(&self.session_state.current_branch)
576            .current_dir(&self.repository_path)
577            .output()
578            .map_err(|e| format!("Failed to merge: {}", e))?;
579
580        if merge_output.status.success() {
581            Ok(())
582        } else {
583            let error = String::from_utf8_lossy(&merge_output.stderr);
584            Err(format!("Failed to merge session branch: {}", error))
585        }
586    }
587}
588
589// Implementation for sub-systems
590
591impl BranchManager {
592    pub fn new(repo_path: PathBuf) -> Result<Self, String> {
593        Ok(BranchManager {
594            repo_path,
595            naming_strategy: BranchNamingStrategy::default(),
596            auto_cleanup: true,
597            max_branch_lifetime_days: 7,
598        })
599    }
600
601    pub fn create_session_branch(&self, session_name: Option<String>) -> Result<String, String> {
602        let branch_name = match &self.naming_strategy {
603            BranchNamingStrategy::SessionTimestamp => {
604                let timestamp = SystemTime::now()
605                    .duration_since(UNIX_EPOCH)
606                    .unwrap()
607                    .as_secs();
608                format!("soma-session-{}", timestamp)
609            }
610            BranchNamingStrategy::SessionId => {
611                format!("soma-session-{}", 
612                    session_name.unwrap_or_else(|| "default".to_string()))
613            }
614            BranchNamingStrategy::UserTimestamp => {
615                let timestamp = SystemTime::now()
616                    .duration_since(UNIX_EPOCH)
617                    .unwrap()
618                    .as_secs();
619                format!("soma-user-{}", timestamp)
620            }
621            BranchNamingStrategy::Custom(pattern) => {
622                pattern.clone()
623            }
624        };
625
626        // Create new branch
627        let output = Command::new("git")
628            .arg("checkout")
629            .arg("-b")
630            .arg(&branch_name)
631            .current_dir(&self.repo_path)
632            .output()
633            .map_err(|e| format!("Failed to create branch: {}", e))?;
634
635        if output.status.success() {
636            Ok(branch_name)
637        } else {
638            let error = String::from_utf8_lossy(&output.stderr);
639            Err(format!("Failed to create branch: {}", error))
640        }
641    }
642}
643
644impl CommitManager {
645    pub fn new(repo_path: PathBuf) -> Result<Self, String> {
646        let mut message_templates = HashMap::new();
647        message_templates.insert("safe".to_string(), "SOMA Safe Edit: {}".to_string());
648        message_templates.insert("critical".to_string(), "SOMA Critical Edit: {}".to_string());
649        message_templates.insert("experimental".to_string(), "SOMA Experimental Edit: {}".to_string());
650        message_templates.insert("cosmetic".to_string(), "SOMA Cosmetic Edit: {}".to_string());
651
652        Ok(CommitManager {
653            repo_path,
654            message_templates,
655            auto_stage: true,
656            sign_commits: false,
657        })
658    }
659
660    pub fn initialize_session(&self, _session_id: &str) -> Result<(), String> {
661        // Initialize session-specific commit settings
662        Ok(())
663    }
664
665    pub fn generate_commit_message(&self, edit: &ModifiableEdit, classification: &ClassifiedEdit) -> String {
666        let edit_type = match &classification.category {
667            EditCategory::Critical { .. } => "critical",
668            EditCategory::Safe { .. } => "safe",
669            EditCategory::Experimental { .. } => "experimental",
670            EditCategory::Cosmetic { .. } => "cosmetic",
671        };
672
673        let default_template = "SOMA Edit: {}".to_string();
674        let template = self.message_templates.get(edit_type)
675            .unwrap_or(&default_template);
676
677        let description = if !classification.reasoning.is_empty() {
678            &classification.reasoning
679        } else {
680            &format!("Edit to {}", edit.base_edit.file)
681        };
682
683        template.replace("{}", description)
684    }
685}
686
687impl ConflictResolver {
688    pub fn new(repo_path: PathBuf) -> Result<Self, String> {
689        Ok(ConflictResolver {
690            repo_path,
691            strategies: vec![ConflictStrategy::Interactive],
692            use_classification: true,
693            cognitive_resolution: true,
694        })
695    }
696}
697
698impl BackupManager {
699    pub fn new(repo_path: PathBuf) -> Result<Self, String> {
700        let backup_dir = repo_path.join(".soma-backups");
701        
702        // Create backup directory if it doesn't exist
703        if !backup_dir.exists() {
704            fs::create_dir_all(&backup_dir)
705                .map_err(|e| format!("Failed to create backup directory: {}", e))?;
706        }
707
708        Ok(BackupManager {
709            repo_path,
710            backup_dir,
711            compress_backups: true,
712            incremental: true,
713        })
714    }
715
716    pub fn create_backup_point(&self, description: String) -> Result<BackupPoint, String> {
717        let timestamp = SystemTime::now();
718        let id = format!("backup-{}", timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs());
719        
720        // Get current commit hash as state hash
721        let hash_output = Command::new("git")
722            .arg("rev-parse")
723            .arg("HEAD")
724            .current_dir(&self.repo_path)
725            .output()
726            .map_err(|e| format!("Failed to get state hash: {}", e))?;
727
728        let state_hash = if hash_output.status.success() {
729            String::from_utf8_lossy(&hash_output.stdout).trim().to_string()
730        } else {
731            "unknown".to_string()
732        };
733
734        // Try to create Git stash as backup (only if there are changes)
735        let _stash_output = Command::new("git")
736            .arg("stash")
737            .arg("push")
738            .arg("-m")
739            .arg(&format!("SOMA Backup: {}", description))
740            .current_dir(&self.repo_path)
741            .output()
742            .map_err(|e| format!("Failed to create backup stash: {}", e))?;
743
744        // Stash command succeeds even if there are no changes to stash
745        // We'll create the backup point regardless
746        Ok(BackupPoint {
747            id,
748            timestamp,
749            state_hash,
750            backup_path: self.backup_dir.clone(),
751            description,
752        })
753    }
754}
755
756// Tests
757#[cfg(test)]
758mod tests {
759    use super::*;
760    use std::fs;
761    use tempfile::TempDir;
762
763    fn create_test_git_repo() -> (TempDir, PathBuf) {
764        let temp_dir = TempDir::new().unwrap();
765        let repo_path = temp_dir.path().to_path_buf();
766        
767        // Initialize git repository
768        Command::new("git")
769            .arg("init")
770            .current_dir(&repo_path)
771            .output()
772            .unwrap();
773            
774        // Configure git user for tests
775        Command::new("git")
776            .args(&["config", "user.name", "SOMA Test"])
777            .current_dir(&repo_path)
778            .output()
779            .unwrap();
780            
781        Command::new("git")
782            .args(&["config", "user.email", "soma@test.com"])
783            .current_dir(&repo_path)
784            .output()
785            .unwrap();
786
787        // Create an initial commit to have a valid Git history
788        let initial_file = repo_path.join("README.md");
789        fs::write(&initial_file, "# SOMA Test Repository\n").unwrap();
790        
791        Command::new("git")
792            .args(&["add", "README.md"])
793            .current_dir(&repo_path)
794            .output()
795            .unwrap();
796            
797        Command::new("git")
798            .args(&["commit", "-m", "Initial commit"])
799            .current_dir(&repo_path)
800            .output()
801            .unwrap();
802
803        (temp_dir, repo_path)
804    }
805
806    #[test]
807    fn test_git_integration_system_creation() {
808        let (_temp_dir, repo_path) = create_test_git_repo();
809        
810        let git_system = GitIntegrationSystem::new(repo_path);
811        assert!(git_system.is_ok());
812    }
813
814    #[test]
815    fn test_session_start_and_end() {
816        let (_temp_dir, repo_path) = create_test_git_repo();
817        
818        let mut git_system = GitIntegrationSystem::new(repo_path).unwrap();
819        
820        // Start session
821        let session_result = git_system.start_session(Some("test-session".to_string()));
822        assert!(session_result.is_ok());
823        
824        // End session
825        let end_result = git_system.end_session(false);
826        assert!(end_result.is_ok());
827    }
828
829    #[test]
830    fn test_branch_manager() {
831        let (_temp_dir, repo_path) = create_test_git_repo();
832        
833        let branch_manager = BranchManager::new(repo_path).unwrap();
834        let branch_name = branch_manager.create_session_branch(Some("test".to_string()));
835        assert!(branch_name.is_ok());
836    }
837
838    #[test]
839    fn test_backup_manager() {
840        let (_temp_dir, repo_path) = create_test_git_repo();
841        
842        let backup_manager = BackupManager::new(repo_path).unwrap();
843        let backup_point = backup_manager.create_backup_point("Test backup".to_string());
844        assert!(backup_point.is_ok());
845    }
846
847    #[test]
848    fn test_conflict_detection() {
849        let (_temp_dir, repo_path) = create_test_git_repo();
850        
851        // Create a file with conflict markers
852        let test_file = repo_path.join("test_conflict.txt");
853        fs::write(&test_file, "line1\n<<<<<<< HEAD\nlocal change\n=======\nremote change\n>>>>>>> branch\nline3").unwrap();
854        
855        let git_system = GitIntegrationSystem::new(repo_path).unwrap();
856        let conflicts = git_system.check_for_conflicts(&test_file.to_string_lossy()).unwrap();
857        assert!(conflicts.is_some());
858    }
859
860    #[test]
861    fn test_git_config_default() {
862        let config = GitConfig::default();
863        assert!(config.auto_commit);
864        assert!(config.session_branches);
865        assert_eq!(config.backup_frequency_minutes, 30);
866    }
867}