Skip to main content

dstack/
cmd_sync.rs

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}