1use crate::config::Config;
2use std::process::Command;
3
4pub fn status(cfg: &Config) -> anyhow::Result<()> {
5 if cfg.repos.tracked.is_empty() {
6 eprintln!("No repos tracked. Add repos to [repos] in config.toml");
7 return Ok(());
8 }
9 println!("{:<20} {:<10} {}", "REPO", "BRANCH", "STATUS");
10 println!("{}", "-".repeat(50));
11 for repo in &cfg.repos.tracked {
12 let path = format!("{}/{}", cfg.repos.root, repo);
13 if !std::path::Path::new(&path).exists() {
14 println!("{:<20} {:<10} {}", repo, "-", "NOT FOUND");
15 continue;
16 }
17 let branch = git_output(&path, &["branch", "--show-current"]);
18 let dirty = git_output(&path, &["status", "--porcelain"]);
19 let dirty_count = dirty.lines().filter(|l| !l.is_empty()).count();
20 let status_str = if dirty_count > 0 {
21 format!("{} dirty file(s)", dirty_count)
22 } else {
23 "clean".into()
24 };
25 println!("{:<20} {:<10} {}", repo, branch.trim(), status_str);
26 }
27 Ok(())
28}
29
30pub fn sync(cfg: &Config, dry_run: bool) -> anyhow::Result<()> {
31 if cfg.repos.tracked.is_empty() {
32 anyhow::bail!("No repos tracked. Add repos to [repos] in config.toml");
33 }
34 for repo in &cfg.repos.tracked {
35 let path = format!("{}/{}", cfg.repos.root, repo);
36 if !std::path::Path::new(&path).exists() {
37 eprintln!("{}: NOT FOUND, skipping", repo);
38 continue;
39 }
40 let dirty = git_output(&path, &["status", "--porcelain"]);
41 if !dirty.trim().is_empty() {
42 eprintln!("{}: dirty — skipping (commit first)", repo);
43 continue;
44 }
45 if dry_run {
46 eprintln!("{}: clean (dry-run, would pull+push)", repo);
47 continue;
48 }
49 eprint!("{}: ", repo);
50 let pull = Command::new("git").args(["-C", &path, "pull", "--ff-only"]).status()?;
51 if pull.success() {
52 let push = Command::new("git").args(["-C", &path, "push"]).status()?;
53 eprintln!("{}", if push.success() { "synced" } else { "push failed" });
54 } else {
55 eprintln!("pull failed (diverged?)");
56 }
57 }
58 Ok(())
59}
60
61fn git_output(repo_path: &str, args: &[&str]) -> String {
62 Command::new("git")
63 .args([&["-C", repo_path], args].concat())
64 .output()
65 .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
66 .unwrap_or_default()
67}