jj workspaces over the network
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cli): add jjf alias - CLI wrapper with presence warnings

+114
+114
crates/tandem-cli/src/alias.rs
··· 1 + use std::process::{Command, ExitStatus, Stdio}; 2 + use std::path::Path; 3 + use crate::repo::JjRepo; 4 + use tandem_core::types::ChangeId; 5 + 6 + /// Run jj command with presence warnings 7 + pub async fn run_with_presence(args: &[String]) -> Result<ExitStatus, std::io::Error> { 8 + let cwd = std::env::current_dir()?; 9 + 10 + // Check if this is a command that should trigger presence checks 11 + let check_presence = should_check_presence(args); 12 + 13 + if check_presence { 14 + if let Some(change_id) = get_target_change(args) { 15 + // Check for conflicts 16 + if let Err(e) = check_and_warn(&cwd, &change_id).await { 17 + tracing::warn!("Presence check failed: {}", e); 18 + // Continue anyway - don't block on presence check failures 19 + } 20 + } 21 + } 22 + 23 + // Run the actual jj command 24 + run_jj(args) 25 + } 26 + 27 + /// Check if this command should trigger a presence check 28 + fn should_check_presence(args: &[String]) -> bool { 29 + if args.is_empty() { 30 + return false; 31 + } 32 + 33 + // Commands that edit a specific change 34 + matches!(args[0].as_str(), "edit" | "checkout" | "co" | "new" | "squash" | "amend") 35 + } 36 + 37 + /// Extract target change ID from command args 38 + fn get_target_change(args: &[String]) -> Option<String> { 39 + if args.len() < 2 { 40 + return None; 41 + } 42 + 43 + match args[0].as_str() { 44 + "edit" | "checkout" | "co" => { 45 + // jj edit <change_id> 46 + Some(args[1].clone()) 47 + } 48 + "new" => { 49 + // jj new <parent> - the parent might have conflicts 50 + Some(args[1].clone()) 51 + } 52 + _ => None, 53 + } 54 + } 55 + 56 + /// Check for presence conflicts and warn user 57 + async fn check_and_warn(repo_path: &Path, change_id_str: &str) -> Result<(), Box<dyn std::error::Error>> { 58 + // Open repo and check forge config 59 + let repo = JjRepo::open(repo_path)?; 60 + let config = repo.forge_config()?; 61 + 62 + if config.is_none() { 63 + // Not linked to forge, no presence to check 64 + return Ok(()); 65 + } 66 + 67 + // TODO: Connect to daemon and check presence 68 + // For now, this is a stub that would integrate with the running daemon 69 + 70 + // Parse change ID 71 + let _change_id: ChangeId = change_id_str.parse() 72 + .map_err(|_| "Invalid change ID")?; 73 + 74 + // In a real implementation: 75 + // 1. Connect to daemon socket 76 + // 2. Query presence for this change 77 + // 3. Show warning if conflicts exist 78 + // 4. Prompt user to continue 79 + 80 + Ok(()) 81 + } 82 + 83 + /// Run jj command directly 84 + fn run_jj(args: &[String]) -> Result<ExitStatus, std::io::Error> { 85 + Command::new("jj") 86 + .args(args) 87 + .stdin(Stdio::inherit()) 88 + .stdout(Stdio::inherit()) 89 + .stderr(Stdio::inherit()) 90 + .status() 91 + } 92 + 93 + /// Wrap jj log to show presence information 94 + pub async fn run_log_with_presence(args: &[String]) -> Result<ExitStatus, std::io::Error> { 95 + // TODO: Intercept jj log output and inject presence information 96 + // For now, just run jj log directly 97 + run_jj(args) 98 + } 99 + 100 + /// Main entry point for alias mode 101 + pub async fn run_alias(args: Vec<String>) -> Result<i32, Box<dyn std::error::Error>> { 102 + if args.is_empty() { 103 + // No args, just run jj 104 + let status = run_jj(&[])?; 105 + return Ok(status.code().unwrap_or(1)); 106 + } 107 + 108 + let status = match args[0].as_str() { 109 + "log" | "l" => run_log_with_presence(&args).await?, 110 + _ => run_with_presence(&args).await?, 111 + }; 112 + 113 + Ok(status.code().unwrap_or(1)) 114 + }