Skip to main content

batuta/stack/publish_status/
git.rs

1//! Git status parsing and version functions.
2//!
3//! Provides utilities for parsing git status and extracting version
4//! information from Cargo.toml files.
5
6use anyhow::{anyhow, Result};
7use std::path::Path;
8
9use super::cache::get_git_head;
10use super::types::{GitStatus, PublishAction};
11
12// ============================================================================
13// PUB-004: Git Status Parsing
14// ============================================================================
15
16/// Get git status for a repo
17pub fn get_git_status(repo_path: &Path) -> Result<GitStatus> {
18    let output = std::process::Command::new("git")
19        .args(["status", "--porcelain"])
20        .current_dir(repo_path)
21        .output()?;
22
23    if !output.status.success() {
24        return Err(anyhow!("git status failed"));
25    }
26
27    let stdout = String::from_utf8_lossy(&output.stdout);
28    let mut status = GitStatus::default();
29
30    for line in stdout.lines() {
31        if line.len() < 2 {
32            continue;
33        }
34        let index = line.chars().next().unwrap_or(' ');
35        let worktree = line.chars().nth(1).unwrap_or(' ');
36
37        match (index, worktree) {
38            ('?', '?') => status.untracked += 1,
39            (i, _) if i != ' ' && i != '?' => status.staged += 1,
40            (_, w) if w != ' ' && w != '?' => status.modified += 1,
41            _ => {}
42        }
43    }
44
45    status.is_clean = status.total_changes() == 0;
46    status.head_sha = get_git_head(repo_path).unwrap_or_default();
47
48    Ok(status)
49}
50
51// ============================================================================
52// PUB-005: Version Parsing
53// ============================================================================
54
55/// Extract version from Cargo.toml
56pub fn get_local_version(repo_path: &Path) -> Result<String> {
57    let cargo_toml = repo_path.join("Cargo.toml");
58    let content = std::fs::read_to_string(&cargo_toml)?;
59
60    for line in content.lines() {
61        if line.starts_with("version") {
62            if let Some(version) = line.split('"').nth(1) {
63                return Ok(version.to_string());
64            }
65        }
66    }
67
68    Err(anyhow!("No version found in Cargo.toml"))
69}
70
71/// Compare versions and determine action
72pub fn determine_action(
73    local: Option<&str>,
74    crates_io: Option<&str>,
75    git_status: &GitStatus,
76) -> PublishAction {
77    match (local, crates_io) {
78        (None, _) => PublishAction::Error,
79        (Some(_), None) => {
80            if git_status.is_clean {
81                PublishAction::NotPublished
82            } else {
83                PublishAction::NeedsCommit
84            }
85        }
86        (Some(local), Some(remote)) => {
87            if !git_status.is_clean {
88                PublishAction::NeedsCommit
89            } else if local == remote {
90                PublishAction::UpToDate
91            } else {
92                // Parse and compare versions
93                match (semver::Version::parse(local), semver::Version::parse(remote)) {
94                    (Ok(l), Ok(r)) if l > r => PublishAction::NeedsPublish,
95                    (Ok(l), Ok(r)) if l < r => PublishAction::LocalBehind,
96                    _ => PublishAction::UpToDate,
97                }
98            }
99        }
100    }
101}