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 link command - connect repo to forge

+79
+79
crates/tandem-cli/src/link.rs
··· 1 + use std::path::Path; 2 + use crate::repo::{JjRepo, ForgeConfig, ForgeSettings, RepoError}; 3 + 4 + #[derive(Debug, thiserror::Error)] 5 + pub enum LinkError { 6 + #[error("Repository error: {0}")] 7 + Repo(#[from] RepoError), 8 + #[error("Already linked to forge: {0}")] 9 + AlreadyLinked(String), 10 + #[error("Forge unreachable: {0}")] 11 + Unreachable(String), 12 + #[error("Authentication failed")] 13 + AuthFailed, 14 + #[error("HTTP error: {0}")] 15 + Http(String), 16 + } 17 + 18 + pub async fn link_repo( 19 + repo_path: &Path, 20 + forge_url: &str, 21 + token: Option<&str>, 22 + ) -> Result<(), LinkError> { 23 + let repo = JjRepo::open(repo_path)?; 24 + 25 + if let Some(existing) = repo.forge_config()? { 26 + return Err(LinkError::AlreadyLinked(existing.forge.url)); 27 + } 28 + 29 + let url = normalize_forge_url(forge_url); 30 + 31 + test_forge_connection(&url, token).await?; 32 + 33 + let config = ForgeConfig { 34 + forge: ForgeSettings { 35 + url: url.clone(), 36 + }, 37 + }; 38 + repo.set_forge_config(&config)?; 39 + 40 + if token.is_some() { 41 + println!("Token provided - in production, this would be stored in system keychain"); 42 + } 43 + 44 + println!("✓ Linked to forge: {}", url); 45 + println!(" Run 'jjf daemon start' to begin syncing"); 46 + 47 + Ok(()) 48 + } 49 + 50 + fn normalize_forge_url(url: &str) -> String { 51 + let mut url = url.to_string(); 52 + 53 + if !url.starts_with("http://") && !url.starts_with("https://") { 54 + url = format!("https://{}", url); 55 + } 56 + 57 + url.trim_end_matches('/').to_string() 58 + } 59 + 60 + async fn test_forge_connection(url: &str, token: Option<&str>) -> Result<(), LinkError> { 61 + let client = reqwest::Client::new(); 62 + 63 + let mut req = client.get(format!("{}/health", url)); 64 + if let Some(token) = token { 65 + req = req.header("Authorization", format!("Bearer {}", token)); 66 + } 67 + 68 + let response = req.send().await 69 + .map_err(|e| LinkError::Unreachable(e.to_string()))?; 70 + 71 + if !response.status().is_success() { 72 + if response.status().as_u16() == 401 { 73 + return Err(LinkError::AuthFailed); 74 + } 75 + return Err(LinkError::Http(format!("Status: {}", response.status()))); 76 + } 77 + 78 + Ok(()) 79 + }