A tool to help managing forked repos with their own history
at main 132 lines 3.6 kB view raw
1use anyhow::{Context, Result}; 2use serde::{Deserialize, Serialize}; 3use std::fs; 4use std::path::Path; 5 6const CONFIG_FILE: &str = "forkme.toml"; 7const LOCK_FILE: &str = "forkme.lock"; 8 9#[derive(Debug, Serialize, Deserialize)] 10pub struct Config { 11 pub upstream: Upstream, 12} 13 14#[derive(Debug, Serialize, Deserialize)] 15pub struct Upstream { 16 pub url: String, 17 pub branch: String, 18} 19 20impl Config { 21 pub fn load() -> Result<Self> { 22 Self::load_from(CONFIG_FILE) 23 } 24 25 pub fn load_from<P: AsRef<Path>>(path: P) -> Result<Self> { 26 let content = fs::read_to_string(path.as_ref()) 27 .with_context(|| format!("Failed to read {}", path.as_ref().display()))?; 28 toml::from_str(&content).with_context(|| "Failed to parse forkme.toml") 29 } 30 31 pub fn save(&self) -> Result<()> { 32 self.save_to(CONFIG_FILE) 33 } 34 35 pub fn save_to<P: AsRef<Path>>(&self, path: P) -> Result<()> { 36 let content = toml::to_string_pretty(self)?; 37 fs::write(path.as_ref(), content) 38 .with_context(|| format!("Failed to write {}", path.as_ref().display()))?; 39 Ok(()) 40 } 41 42 pub fn exists() -> bool { 43 Path::new(CONFIG_FILE).exists() 44 } 45} 46 47pub fn load_lock() -> Result<Option<String>> { 48 let path = Path::new(LOCK_FILE); 49 if !path.exists() { 50 return Ok(None); 51 } 52 let content = 53 fs::read_to_string(path).with_context(|| format!("Failed to read {}", LOCK_FILE))?; 54 let sha = content.trim().to_string(); 55 if sha.is_empty() { 56 return Ok(None); 57 } 58 Ok(Some(sha)) 59} 60 61pub fn save_lock(sha: &str) -> Result<()> { 62 fs::write(LOCK_FILE, format!("{}\n", sha)) 63 .with_context(|| format!("Failed to write {}", LOCK_FILE))?; 64 Ok(()) 65} 66 67#[cfg(test)] 68mod tests { 69 use super::*; 70 use tempfile::NamedTempFile; 71 72 #[test] 73 fn test_config_save_and_load() { 74 let temp_file = NamedTempFile::new().unwrap(); 75 let path = temp_file.path(); 76 77 let config = Config { 78 upstream: Upstream { 79 url: "https://github.com/test/repo.git".to_string(), 80 branch: "main".to_string(), 81 }, 82 }; 83 84 config.save_to(path).unwrap(); 85 let loaded = Config::load_from(path).unwrap(); 86 87 assert_eq!(loaded.upstream.url, "https://github.com/test/repo.git"); 88 assert_eq!(loaded.upstream.branch, "main"); 89 } 90 91 #[test] 92 fn test_config_toml_format() { 93 let temp_file = NamedTempFile::new().unwrap(); 94 let path = temp_file.path(); 95 96 let config = Config { 97 upstream: Upstream { 98 url: "https://github.com/test/repo.git".to_string(), 99 branch: "develop".to_string(), 100 }, 101 }; 102 103 config.save_to(path).unwrap(); 104 let content = fs::read_to_string(path).unwrap(); 105 106 assert!(content.contains("[upstream]")); 107 assert!(content.contains("url = \"https://github.com/test/repo.git\"")); 108 assert!(content.contains("branch = \"develop\"")); 109 } 110 111 #[test] 112 fn test_load_invalid_toml() { 113 let temp_file = NamedTempFile::new().unwrap(); 114 let path = temp_file.path(); 115 116 fs::write(path, "invalid toml content {{{").unwrap(); 117 let result = Config::load_from(path); 118 119 assert!(result.is_err()); 120 } 121 122 #[test] 123 fn test_load_missing_field() { 124 let temp_file = NamedTempFile::new().unwrap(); 125 let path = temp_file.path(); 126 127 fs::write(path, "[upstream]\nurl = \"test\"").unwrap(); 128 let result = Config::load_from(path); 129 130 assert!(result.is_err()); 131 } 132}