git_worktree_manager/
update.rs1use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
9
10#[derive(Debug, Serialize, Deserialize, Default)]
12struct UpdateCache {
13 last_check: String,
14 latest_version: Option<String>,
15}
16
17fn get_cache_path() -> PathBuf {
18 dirs::cache_dir()
19 .unwrap_or_else(|| dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")))
20 .join("git-worktree-manager")
21 .join("update_check.json")
22}
23
24fn load_cache() -> UpdateCache {
25 let path = get_cache_path();
26 if !path.exists() {
27 return UpdateCache::default();
28 }
29 std::fs::read_to_string(&path)
30 .ok()
31 .and_then(|c| serde_json::from_str(&c).ok())
32 .unwrap_or_default()
33}
34
35fn save_cache(cache: &UpdateCache) {
36 let path = get_cache_path();
37 if let Some(parent) = path.parent() {
38 let _ = std::fs::create_dir_all(parent);
39 }
40 if let Ok(content) = serde_json::to_string_pretty(cache) {
41 let _ = std::fs::write(&path, content);
42 }
43}
44
45fn today_str() -> String {
46 crate::session::chrono_now_iso_pub()
48 .split('T')
49 .next()
50 .unwrap_or("")
51 .to_string()
52}
53
54fn should_check() -> bool {
55 let config = crate::config::load_config().unwrap_or_default();
56 if !config.update.auto_check {
57 return false;
58 }
59 let cache = load_cache();
60 cache.last_check != today_str()
61}
62
63pub fn check_for_update_if_needed() {
65 if !should_check() {
66 return;
67 }
68
69 if let Some(latest) = fetch_latest_version() {
71 let cache = UpdateCache {
72 last_check: today_str(),
73 latest_version: Some(latest.clone()),
74 };
75 save_cache(&cache);
76
77 if is_newer(&latest, CURRENT_VERSION) {
78 eprintln!(
79 "\ngit-worktree-manager {} is available (current: {})",
80 latest, CURRENT_VERSION
81 );
82 eprintln!("Run 'cw upgrade' to update.\n");
83 }
84 } else {
85 let cache = UpdateCache {
87 last_check: today_str(),
88 latest_version: None,
89 };
90 save_cache(&cache);
91 }
92}
93
94fn fetch_latest_version() -> Option<String> {
96 let output = std::process::Command::new("curl")
99 .args([
100 "-s",
101 "-H",
102 "Accept: application/vnd.github+json",
103 "https://api.github.com/repos/DaveDev42/git-worktree-manager/releases/latest",
104 ])
105 .output()
106 .ok()?;
107
108 if !output.status.success() {
109 return None;
110 }
111
112 let body = String::from_utf8_lossy(&output.stdout);
113 let json: serde_json::Value = serde_json::from_str(&body).ok()?;
114 let tag = json.get("tag_name")?.as_str()?;
115
116 Some(tag.strip_prefix('v').unwrap_or(tag).to_string())
118}
119
120fn is_newer(latest: &str, current: &str) -> bool {
122 let parse = |s: &str| -> Vec<u32> { s.split('.').filter_map(|p| p.parse().ok()).collect() };
123 let l = parse(latest);
124 let c = parse(current);
125 l > c
126}
127
128pub fn upgrade() {
130 println!("git-worktree-manager v{}", CURRENT_VERSION);
131
132 if let Some(latest) = fetch_latest_version() {
133 if is_newer(&latest, CURRENT_VERSION) {
134 println!("New version available: v{}", latest);
135 println!("\nTo upgrade:");
136 println!(
137 " Download from: https://github.com/DaveDev42/git-worktree-manager/releases/latest"
138 );
139
140 #[cfg(target_os = "macos")]
141 println!(" Or: brew upgrade git-worktree-manager");
142 } else {
143 println!("Already up to date.");
144 }
145 } else {
146 println!("Could not check for updates. Check your internet connection.");
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_is_newer() {
156 assert!(is_newer("0.2.0", "0.1.0"));
157 assert!(is_newer("1.0.0", "0.10.0"));
158 assert!(!is_newer("0.1.0", "0.1.0"));
159 assert!(!is_newer("0.1.0", "0.2.0"));
160 }
161}