1use std::io::{self, Write};
2use crossterm::{
3 event::{self, Event, KeyCode},
4 execute,
5 style::{Color, Print, ResetColor, SetForegroundColor},
6 terminal::{self, Clear, ClearType},
7 cursor,
8};
9use crate::git_integration::{
10 GitIntegrationSystem, GitOperationResult, ConflictStrategy
11};
12use crate::edit_control::ModifiableEdit;
13use crate::classification::ClassifiedEdit;
14
15pub struct GitIntegrationCLI {
18 pub git_system: GitIntegrationSystem,
20 pub view_mode: GitViewMode,
22 pub active_panel: GitPanel,
24 pub status_message: String,
26 pub show_help: bool,
28 pub pending_edits: Vec<(ModifiableEdit, ClassifiedEdit)>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34pub enum GitViewMode {
35 SessionOverview,
37 BranchManagement,
39 CommitHistory,
41 ConflictResolution,
43 BackupManagement,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum GitPanel {
50 SessionStatus,
52 Operations,
54 FileChanges,
56 Configuration,
58}
59
60impl GitIntegrationCLI {
61 pub fn new(repository_path: std::path::PathBuf) -> Result<Self, String> {
63 let git_system = GitIntegrationSystem::new(repository_path)?;
64
65 Ok(GitIntegrationCLI {
66 git_system,
67 view_mode: GitViewMode::SessionOverview,
68 active_panel: GitPanel::SessionStatus,
69 status_message: "Git Integration ready".to_string(),
70 show_help: false,
71 pending_edits: Vec::new(),
72 })
73 }
74
75 pub fn start_interactive_session(&mut self) -> Result<(), String> {
77 terminal::enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
79
80 let result = self.run_interactive_loop();
81
82 terminal::disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
84 execute!(io::stdout(), Clear(ClearType::All), cursor::MoveTo(0, 0)).ok();
85
86 result
87 }
88
89 fn run_interactive_loop(&mut self) -> Result<(), String> {
91 loop {
92 self.render_interface()?;
94
95 if let Ok(Event::Key(key_event)) = event::read() {
97 match key_event.code {
98 KeyCode::Char('q') | KeyCode::Esc => {
99 break;
100 }
101 KeyCode::Char('h') => {
102 self.show_help = !self.show_help;
103 }
104 KeyCode::Char('s') => {
105 self.handle_start_session()?;
106 }
107 KeyCode::Char('e') => {
108 self.handle_end_session()?;
109 }
110 KeyCode::Char('c') => {
111 self.handle_commit_changes()?;
112 }
113 KeyCode::Char('b') => {
114 self.handle_create_backup()?;
115 }
116 KeyCode::Char('m') => {
117 self.handle_merge_session()?;
118 }
119 KeyCode::Tab => {
120 self.cycle_panels();
121 }
122 KeyCode::F(1) => {
123 self.view_mode = GitViewMode::SessionOverview;
124 }
125 KeyCode::F(2) => {
126 self.view_mode = GitViewMode::BranchManagement;
127 }
128 KeyCode::F(3) => {
129 self.view_mode = GitViewMode::CommitHistory;
130 }
131 KeyCode::F(4) => {
132 self.view_mode = GitViewMode::ConflictResolution;
133 }
134 KeyCode::F(5) => {
135 self.view_mode = GitViewMode::BackupManagement;
136 }
137 KeyCode::Char('1') => {
138 self.active_panel = GitPanel::SessionStatus;
139 }
140 KeyCode::Char('2') => {
141 self.active_panel = GitPanel::Operations;
142 }
143 KeyCode::Char('3') => {
144 self.active_panel = GitPanel::FileChanges;
145 }
146 KeyCode::Char('4') => {
147 self.active_panel = GitPanel::Configuration;
148 }
149 _ => {}
150 }
151 }
152 }
153
154 Ok(())
155 }
156
157 fn render_interface(&self) -> Result<(), String> {
159 execute!(io::stdout(), Clear(ClearType::All), cursor::MoveTo(0, 0))
160 .map_err(|e| format!("Failed to clear screen: {}", e))?;
161
162 self.render_header()?;
164
165 match self.view_mode {
167 GitViewMode::SessionOverview => self.render_session_overview()?,
168 GitViewMode::BranchManagement => self.render_branch_management()?,
169 GitViewMode::CommitHistory => self.render_commit_history()?,
170 GitViewMode::ConflictResolution => self.render_conflict_resolution()?,
171 GitViewMode::BackupManagement => self.render_backup_management()?,
172 }
173
174 self.render_footer()?;
176
177 if self.show_help {
179 self.render_help_panel()?;
180 }
181
182 io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?;
183 Ok(())
184 }
185
186 fn render_header(&self) -> Result<(), String> {
188 execute!(io::stdout(),
189 SetForegroundColor(Color::Cyan),
190 Print("╔══════════════════════════════════════════════════════════════════════════════╗\n"),
191 Print("║ 🔧 SOMA-CORE Git Integration Interface ║\n"),
192 Print("╚══════════════════════════════════════════════════════════════════════════════╝\n"),
193 ResetColor
194 ).map_err(|e| format!("Failed to render header: {}", e))?;
195
196 execute!(io::stdout(),
198 SetForegroundColor(Color::Yellow),
199 Print(&format!("Session: {} | Branch: {} | View: {:?}\n",
200 self.git_system.session_state.session_id,
201 self.git_system.session_state.current_branch,
202 self.view_mode
203 )),
204 ResetColor,
205 Print("\n")
206 ).map_err(|e| format!("Failed to render session status: {}", e))?;
207
208 Ok(())
209 }
210
211 fn render_session_overview(&self) -> Result<(), String> {
213 let session = &self.git_system.session_state;
214
215 execute!(io::stdout(),
216 SetForegroundColor(Color::Green),
217 Print("📊 Session Overview\n"),
218 Print("═══════════════════\n"),
219 ResetColor,
220 Print(&format!("Session ID: {}\n", session.session_id)),
221 Print(&format!("Start Time: {:?}\n", session.start_time)),
222 Print(&format!("Current Branch: {}\n", session.current_branch)),
223 Print(&format!("Original Branch: {}\n", session.original_branch)),
224 Print(&format!("Edits Applied: {}\n", session.edits_applied.len())),
225 Print(&format!("Commits: {}\n", session.commit_history.len())),
226 Print(&format!("Backup Points: {}\n", session.backup_points.len())),
227 Print("\n"),
228
229 SetForegroundColor(Color::Cyan),
230 Print("🔧 Git Configuration\n"),
231 Print("═══════════════════\n"),
232 ResetColor,
233 Print(&format!("Auto Commit: {}\n", self.git_system.config.auto_commit)),
234 Print(&format!("Session Branches: {}\n", self.git_system.config.session_branches)),
235 Print(&format!("Backup Frequency: {} minutes\n", self.git_system.config.backup_frequency_minutes)),
236 Print(&format!("CI/CD Integration: {}\n", self.git_system.config.cicd_integration)),
237 Print(&format!("Conflict Strategy: {:?}\n", self.git_system.config.conflict_strategy)),
238 Print("\n")
239 ).map_err(|e| format!("Failed to render session overview: {}", e))?;
240
241 if !self.pending_edits.is_empty() {
243 execute!(io::stdout(),
244 SetForegroundColor(Color::Yellow),
245 Print("📝 Pending Edits\n"),
246 Print("════════════════\n"),
247 ResetColor
248 ).map_err(|e| format!("Failed to render pending edits header: {}", e))?;
249
250 for (i, (edit, classification)) in self.pending_edits.iter().enumerate() {
251 let classification_color = match &classification.category {
252 crate::classification::EditCategory::Critical { .. } => Color::Red,
253 crate::classification::EditCategory::Experimental { .. } => Color::Yellow,
254 crate::classification::EditCategory::Safe { .. } => Color::Green,
255 crate::classification::EditCategory::Cosmetic { .. } => Color::Blue,
256 };
257
258 execute!(io::stdout(),
259 SetForegroundColor(classification_color),
260 Print(&format!("{}. {} - {:?}\n",
261 i + 1,
262 edit.base_edit.file,
263 classification.category
264 )),
265 ResetColor
266 ).map_err(|e| format!("Failed to render pending edit: {}", e))?;
267 }
268 execute!(io::stdout(), Print("\n")).ok();
269 }
270
271 Ok(())
272 }
273
274 fn render_branch_management(&self) -> Result<(), String> {
276 execute!(io::stdout(),
277 SetForegroundColor(Color::Green),
278 Print("🌿 Branch Management\n"),
279 Print("══════════════════\n"),
280 ResetColor,
281 Print(&format!("Current Branch: {}\n", self.git_system.session_state.current_branch)),
282 Print(&format!("Original Branch: {}\n", self.git_system.session_state.original_branch)),
283 Print(&format!("Branch Strategy: {:?}\n", self.git_system.branch_manager.naming_strategy)),
284 Print(&format!("Auto Cleanup: {}\n", self.git_system.branch_manager.auto_cleanup)),
285 Print("\n"),
286
287 SetForegroundColor(Color::Cyan),
288 Print("Available Operations:\n"),
289 ResetColor,
290 Print("• Create new session branch\n"),
291 Print("• Switch branch\n"),
292 Print("• Merge session to main\n"),
293 Print("• Delete session branch\n"),
294 Print("\n")
295 ).map_err(|e| format!("Failed to render branch management: {}", e))?;
296
297 Ok(())
298 }
299
300 fn render_commit_history(&self) -> Result<(), String> {
302 execute!(io::stdout(),
303 SetForegroundColor(Color::Green),
304 Print("📚 Commit History\n"),
305 Print("═══════════════\n"),
306 ResetColor
307 ).map_err(|e| format!("Failed to render commit history header: {}", e))?;
308
309 if self.git_system.session_state.commit_history.is_empty() {
310 execute!(io::stdout(),
311 SetForegroundColor(Color::Yellow),
312 Print("No commits in current session\n"),
313 ResetColor
314 ).map_err(|e| format!("Failed to render no commits message: {}", e))?;
315 } else {
316 for (i, commit) in self.git_system.session_state.commit_history.iter().enumerate() {
317 execute!(io::stdout(),
318 SetForegroundColor(Color::Cyan),
319 Print(&format!("{}. {}\n", i + 1, &commit.hash[..8])),
320 ResetColor,
321 Print(&format!(" Message: {}\n", commit.message)),
322 Print(&format!(" Files: {}\n", commit.files_changed.join(", "))),
323 Print(&format!(" Time: {:?}\n", commit.timestamp)),
324 Print("\n")
325 ).map_err(|e| format!("Failed to render commit: {}", e))?;
326 }
327 }
328
329 Ok(())
330 }
331
332 fn render_conflict_resolution(&self) -> Result<(), String> {
334 execute!(io::stdout(),
335 SetForegroundColor(Color::Red),
336 Print("⚠️ Conflict Resolution\n"),
337 Print("═════════════════════\n"),
338 ResetColor,
339 Print("Conflict Resolution Strategies:\n"),
340 Print("• Prefer Local Changes\n"),
341 Print("• Prefer Remote Changes\n"),
342 Print("• Interactive Resolution\n"),
343 Print("• Classification-Based Resolution\n"),
344 Print("• Cognitive Operator Resolution\n"),
345 Print("\n"),
346
347 SetForegroundColor(Color::Yellow),
348 Print("Current Strategy: "),
349 ResetColor,
350 Print(&format!("{:?}\n", self.git_system.conflict_resolver.strategies.first().unwrap_or(&ConflictStrategy::Interactive))),
351 Print("\n")
352 ).map_err(|e| format!("Failed to render conflict resolution: {}", e))?;
353
354 Ok(())
355 }
356
357 fn render_backup_management(&self) -> Result<(), String> {
359 execute!(io::stdout(),
360 SetForegroundColor(Color::Green),
361 Print("💾 Backup Management\n"),
362 Print("══════════════════\n"),
363 ResetColor
364 ).map_err(|e| format!("Failed to render backup management header: {}", e))?;
365
366 if self.git_system.session_state.backup_points.is_empty() {
367 execute!(io::stdout(),
368 SetForegroundColor(Color::Yellow),
369 Print("No backup points created\n"),
370 ResetColor
371 ).map_err(|e| format!("Failed to render no backups message: {}", e))?;
372 } else {
373 for (i, backup) in self.git_system.session_state.backup_points.iter().enumerate() {
374 execute!(io::stdout(),
375 SetForegroundColor(Color::Cyan),
376 Print(&format!("{}. Backup: {}\n", i + 1, backup.id)),
377 ResetColor,
378 Print(&format!(" Description: {}\n", backup.description)),
379 Print(&format!(" State Hash: {}\n", &backup.state_hash[..8])),
380 Print(&format!(" Time: {:?}\n", backup.timestamp)),
381 Print("\n")
382 ).map_err(|e| format!("Failed to render backup: {}", e))?;
383 }
384 }
385
386 execute!(io::stdout(),
387 SetForegroundColor(Color::Cyan),
388 Print("Backup Configuration:\n"),
389 ResetColor,
390 Print(&format!("Compression: {}\n", self.git_system.backup_manager.compress_backups)),
391 Print(&format!("Incremental: {}\n", self.git_system.backup_manager.incremental)),
392 Print("\n")
393 ).map_err(|e| format!("Failed to render backup config: {}", e))?;
394
395 Ok(())
396 }
397
398 fn render_footer(&self) -> Result<(), String> {
400 execute!(io::stdout(),
401 SetForegroundColor(Color::Cyan),
402 Print("─".repeat(80)),
403 Print("\n"),
404 Print("Commands: [s]tart session | [e]nd session | [c]ommit | [b]ackup | [m]erge | [h]elp | [q]uit\n"),
405 Print("Views: F1-Overview | F2-Branches | F3-Commits | F4-Conflicts | F5-Backups | Tab-Cycle Panels\n"),
406 ResetColor
407 ).map_err(|e| format!("Failed to render footer: {}", e))?;
408
409 if !self.status_message.is_empty() {
411 execute!(io::stdout(),
412 SetForegroundColor(Color::Yellow),
413 Print(&format!("Status: {}\n", self.status_message)),
414 ResetColor
415 ).map_err(|e| format!("Failed to render status: {}", e))?;
416 }
417
418 Ok(())
419 }
420
421 fn render_help_panel(&self) -> Result<(), String> {
423 execute!(io::stdout(),
424 cursor::MoveTo(20, 8),
425 SetForegroundColor(Color::White),
426 Print("╔═══════════════════════════════════════╗\n"),
427 cursor::MoveTo(20, 9),
428 Print("║ 🔧 Git Help ║\n"),
429 cursor::MoveTo(20, 10),
430 Print("╠═══════════════════════════════════════╣\n"),
431 cursor::MoveTo(20, 11),
432 Print("║ s - Start new Git session ║\n"),
433 cursor::MoveTo(20, 12),
434 Print("║ e - End current session ║\n"),
435 cursor::MoveTo(20, 13),
436 Print("║ c - Commit current changes ║\n"),
437 cursor::MoveTo(20, 14),
438 Print("║ b - Create backup point ║\n"),
439 cursor::MoveTo(20, 15),
440 Print("║ m - Merge session to main ║\n"),
441 cursor::MoveTo(20, 16),
442 Print("║ F1-F5 - Switch view modes ║\n"),
443 cursor::MoveTo(20, 17),
444 Print("║ Tab - Cycle through panels ║\n"),
445 cursor::MoveTo(20, 18),
446 Print("║ h - Toggle this help ║\n"),
447 cursor::MoveTo(20, 19),
448 Print("║ q/Esc - Quit ║\n"),
449 cursor::MoveTo(20, 20),
450 Print("╚═══════════════════════════════════════╝\n"),
451 ResetColor
452 ).map_err(|e| format!("Failed to render help panel: {}", e))?;
453
454 Ok(())
455 }
456
457 fn cycle_panels(&mut self) {
459 self.active_panel = match self.active_panel {
460 GitPanel::SessionStatus => GitPanel::Operations,
461 GitPanel::Operations => GitPanel::FileChanges,
462 GitPanel::FileChanges => GitPanel::Configuration,
463 GitPanel::Configuration => GitPanel::SessionStatus,
464 };
465 }
466
467 fn handle_start_session(&mut self) -> Result<(), String> {
469 match self.git_system.start_session(None) {
470 Ok(message) => {
471 self.status_message = message;
472 }
473 Err(e) => {
474 self.status_message = format!("Error starting session: {}", e);
475 }
476 }
477 Ok(())
478 }
479
480 fn handle_end_session(&mut self) -> Result<(), String> {
482 match self.git_system.end_session(false) {
483 Ok(message) => {
484 self.status_message = message;
485 }
486 Err(e) => {
487 self.status_message = format!("Error ending session: {}", e);
488 }
489 }
490 Ok(())
491 }
492
493 fn handle_commit_changes(&mut self) -> Result<(), String> {
495 let message = "SOMA Interactive Commit";
496 match self.git_system.commit_current_changes(message) {
497 Ok(hash) => {
498 self.status_message = format!("Committed successfully: {}", &hash[..8]);
499 }
500 Err(e) => {
501 self.status_message = format!("Commit error: {}", e);
502 }
503 }
504 Ok(())
505 }
506
507 fn handle_create_backup(&mut self) -> Result<(), String> {
509 match self.git_system.backup_manager.create_backup_point("Interactive backup".to_string()) {
510 Ok(backup) => {
511 self.status_message = format!("Backup created: {}", backup.id);
512 let mut session_state = self.git_system.session_state.clone();
514 session_state.backup_points.push(backup);
515 self.git_system.session_state = session_state;
516 }
517 Err(e) => {
518 self.status_message = format!("Backup error: {}", e);
519 }
520 }
521 Ok(())
522 }
523
524 fn handle_merge_session(&mut self) -> Result<(), String> {
526 match self.git_system.merge_session_to_main() {
527 Ok(()) => {
528 self.status_message = "Session merged to main successfully".to_string();
529 }
530 Err(e) => {
531 self.status_message = format!("Merge error: {}", e);
532 }
533 }
534 Ok(())
535 }
536
537 pub fn add_pending_edits(&mut self, edits: Vec<(ModifiableEdit, ClassifiedEdit)>) {
539 self.pending_edits.extend(edits);
540 }
541
542 pub fn process_pending_edits(&mut self) -> Result<Vec<GitOperationResult>, String> {
544 if self.pending_edits.is_empty() {
545 return Ok(Vec::new());
546 }
547
548 let (edits, classifications): (Vec<_>, Vec<_>) = self.pending_edits.drain(..).unzip();
549
550 let results = self.git_system.apply_edits_with_git(edits, classifications)?;
551
552 self.status_message = format!("Processed {} edits with Git integration", results.len());
553
554 Ok(results)
555 }
556}
557
558pub fn interactive_git_workflow_decision(
560 edits: Vec<ModifiableEdit>,
561 classifications: Vec<ClassifiedEdit>,
562 repository_path: std::path::PathBuf
563) -> Result<bool, String> {
564 println!("🔧 Git Integration Available");
565 println!("════════════════════════════");
566 println!("Would you like to use Git integration for these edits?");
567 println!();
568 println!("Git integration provides:");
569 println!("• Automatic session branching");
570 println!("• Intelligent commit strategies based on edit classification");
571 println!("• Conflict resolution with cognitive operators");
572 println!("• Automated backup points");
573 println!("• CI/CD pipeline integration");
574 println!();
575 print!("Use Git integration? [y/N]: ");
576 io::stdout().flush().map_err(|e| format!("Failed to flush stdout: {}", e))?;
577
578 let mut input = String::new();
579 io::stdin().read_line(&mut input).map_err(|e| format!("Failed to read input: {}", e))?;
580
581 if input.trim().to_lowercase().starts_with('y') {
582 let mut git_cli = GitIntegrationCLI::new(repository_path)?;
584 git_cli.add_pending_edits(edits.into_iter().zip(classifications).collect());
585
586 println!("Launching Git Integration interface...");
587 git_cli.start_interactive_session()?;
588
589 let results = git_cli.process_pending_edits()?;
591
592 println!("Git integration completed with {} operations", results.len());
593 Ok(true)
594 } else {
595 println!("Proceeding without Git integration");
596 Ok(false)
597 }
598}
599
600#[cfg(test)]
602mod tests {
603 use super::*;
604 use tempfile::TempDir;
605 use std::process::Command;
606
607 fn create_test_git_repo() -> (TempDir, std::path::PathBuf) {
608 let temp_dir = TempDir::new().unwrap();
609 let repo_path = temp_dir.path().to_path_buf();
610
611 Command::new("git")
613 .arg("init")
614 .current_dir(&repo_path)
615 .output()
616 .unwrap();
617
618 Command::new("git")
620 .args(&["config", "user.name", "SOMA Test"])
621 .current_dir(&repo_path)
622 .output()
623 .unwrap();
624
625 Command::new("git")
626 .args(&["config", "user.email", "soma@test.com"])
627 .current_dir(&repo_path)
628 .output()
629 .unwrap();
630
631 (temp_dir, repo_path)
632 }
633
634 #[test]
635 fn test_git_integration_cli_creation() {
636 let (_temp_dir, repo_path) = create_test_git_repo();
637
638 let git_cli = GitIntegrationCLI::new(repo_path);
639 assert!(git_cli.is_ok());
640 }
641
642 #[test]
643 fn test_view_mode_cycling() {
644 let (_temp_dir, repo_path) = create_test_git_repo();
645
646 let mut git_cli = GitIntegrationCLI::new(repo_path).unwrap();
647
648 assert_eq!(git_cli.active_panel, GitPanel::SessionStatus);
650 git_cli.cycle_panels();
651 assert_eq!(git_cli.active_panel, GitPanel::Operations);
652 git_cli.cycle_panels();
653 assert_eq!(git_cli.active_panel, GitPanel::FileChanges);
654 }
655
656 #[test]
657 fn test_session_operations() {
658 let (_temp_dir, repo_path) = create_test_git_repo();
659
660 let mut git_cli = GitIntegrationCLI::new(repo_path).unwrap();
661
662 let start_result = git_cli.handle_start_session();
664 assert!(start_result.is_ok());
665
666 let backup_result = git_cli.handle_create_backup();
668 assert!(backup_result.is_ok());
669
670 let end_result = git_cli.handle_end_session();
672 assert!(end_result.is_ok());
673 }
674
675 #[test]
676 fn test_pending_edits_management() {
677 let (_temp_dir, repo_path) = create_test_git_repo();
678
679 let git_cli = GitIntegrationCLI::new(repo_path).unwrap();
680
681 assert_eq!(git_cli.pending_edits.len(), 0);
683
684 }
687}