Skip to main content

git_workflow/pool/
inventory.rs

1//! Pool inventory types and JSON persistence
2
3use std::fs;
4use std::path::Path;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::{GwError, Result};
9
10/// Current inventory format version
11const INVENTORY_VERSION: u32 = 1;
12
13/// Status of a worktree in the pool
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum WorktreeStatus {
17    Available,
18    Acquired,
19}
20
21impl std::fmt::Display for WorktreeStatus {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            WorktreeStatus::Available => write!(f, "available"),
25            WorktreeStatus::Acquired => write!(f, "acquired"),
26        }
27    }
28}
29
30/// A single worktree entry in the pool
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct PoolEntry {
33    pub name: String,
34    pub path: String,
35    pub branch: String,
36    pub status: WorktreeStatus,
37    pub created_at: u64,
38    pub acquired_at: Option<u64>,
39    pub acquired_by: Option<u32>,
40}
41
42/// The pool inventory stored as JSON
43#[derive(Debug, Serialize, Deserialize)]
44pub struct Inventory {
45    pub version: u32,
46    pub worktrees: Vec<PoolEntry>,
47}
48
49impl Default for Inventory {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl Inventory {
56    /// Create a new empty inventory
57    pub fn new() -> Self {
58        Self {
59            version: INVENTORY_VERSION,
60            worktrees: Vec::new(),
61        }
62    }
63
64    /// Load inventory from a file, or create a new one if it doesn't exist
65    pub fn load(path: &Path) -> Result<Self> {
66        if !path.exists() {
67            return Ok(Self::new());
68        }
69        let data = fs::read_to_string(path)?;
70        serde_json::from_str(&data)
71            .map_err(|e| GwError::Other(format!("Failed to parse inventory: {e}")))
72    }
73
74    /// Save inventory to a file
75    pub fn save(&self, path: &Path) -> Result<()> {
76        let data = serde_json::to_string_pretty(self)
77            .map_err(|e| GwError::Other(format!("Failed to serialize inventory: {e}")))?;
78        fs::write(path, data)?;
79        Ok(())
80    }
81
82    /// Count worktrees with a given status
83    pub fn count_by_status(&self, status: &WorktreeStatus) -> usize {
84        self.worktrees
85            .iter()
86            .filter(|w| w.status == *status)
87            .count()
88    }
89
90    /// Find the next available pool name (pool-NNN)
91    pub fn next_name(&self) -> String {
92        let max = self
93            .worktrees
94            .iter()
95            .filter_map(|w| w.name.strip_prefix("pool-"))
96            .filter_map(|n| n.parse::<u32>().ok())
97            .max()
98            .unwrap_or(0);
99        format!("pool-{:03}", max + 1)
100    }
101
102    /// Find the first available worktree
103    pub fn find_available(&self) -> Option<usize> {
104        self.worktrees
105            .iter()
106            .position(|w| w.status == WorktreeStatus::Available)
107    }
108
109    /// Find a worktree by name or path
110    pub fn find_by_name_or_path(&self, identifier: &str) -> Option<usize> {
111        self.worktrees
112            .iter()
113            .position(|w| w.name == identifier || w.path == identifier)
114    }
115}