git_workflow/pool/
inventory.rs1use std::fs;
4use std::path::Path;
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::{GwError, Result};
9
10const INVENTORY_VERSION: u32 = 1;
12
13#[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#[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#[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 pub fn new() -> Self {
58 Self {
59 version: INVENTORY_VERSION,
60 worktrees: Vec::new(),
61 }
62 }
63
64 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 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 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 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 pub fn find_available(&self) -> Option<usize> {
104 self.worktrees
105 .iter()
106 .position(|w| w.status == WorktreeStatus::Available)
107 }
108
109 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}