Skip to main content

cascade_cli/stack/
sync_state.rs

1use crate::errors::{CascadeError, Result};
2use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4
5/// State for an in-progress sync operation
6#[derive(Serialize, Deserialize, Debug, Clone)]
7pub struct SyncState {
8    /// ID of the stack being synced
9    pub stack_id: String,
10    /// Name of the stack
11    pub stack_name: String,
12    /// Branch user was on before sync started
13    pub original_branch: String,
14    /// Base branch being rebased onto
15    pub target_base: String,
16    /// Entry IDs that still need to be processed
17    pub remaining_entry_ids: Vec<String>,
18    /// Entry currently being cherry-picked (the one with conflicts)
19    pub current_entry_id: String,
20    /// Current entry's branch name
21    pub current_entry_branch: String,
22    /// Temp branch for current entry
23    pub current_temp_branch: String,
24    /// All temp branches created so far (for cleanup)
25    pub temp_branches: Vec<String>,
26}
27
28impl SyncState {
29    fn state_path(repo_root: &Path) -> Result<PathBuf> {
30        Ok(crate::git::resolve_git_dir(repo_root)?.join("CASCADE_SYNC_STATE"))
31    }
32
33    /// Save sync state to disk
34    pub fn save(&self, repo_root: &Path) -> Result<()> {
35        let state_path = Self::state_path(repo_root)?;
36        let json = serde_json::to_string_pretty(self)
37            .map_err(|e| CascadeError::config(format!("Failed to serialize sync state: {e}")))?;
38
39        std::fs::write(&state_path, json)
40            .map_err(|e| CascadeError::config(format!("Failed to write sync state: {e}")))?;
41
42        tracing::debug!("Saved sync state to {:?}", state_path);
43        Ok(())
44    }
45
46    /// Load sync state from disk
47    pub fn load(repo_root: &Path) -> Result<Self> {
48        let state_path = Self::state_path(repo_root)?;
49
50        if !state_path.exists() {
51            return Err(CascadeError::config(
52                "No in-progress sync found. Nothing to continue.".to_string(),
53            ));
54        }
55
56        let json = std::fs::read_to_string(&state_path)
57            .map_err(|e| CascadeError::config(format!("Failed to read sync state: {e}")))?;
58
59        let state: Self = serde_json::from_str(&json)
60            .map_err(|e| CascadeError::config(format!("Failed to parse sync state: {e}")))?;
61
62        tracing::debug!("Loaded sync state from {:?}", state_path);
63        Ok(state)
64    }
65
66    /// Delete sync state file
67    pub fn delete(repo_root: &Path) -> Result<()> {
68        let state_path = Self::state_path(repo_root)?;
69
70        if state_path.exists() {
71            std::fs::remove_file(&state_path)
72                .map_err(|e| CascadeError::config(format!("Failed to delete sync state: {e}")))?;
73            tracing::debug!("Deleted sync state file");
74        }
75
76        Ok(())
77    }
78
79    /// Check if sync state exists
80    pub fn exists(repo_root: &Path) -> bool {
81        crate::git::resolve_git_dir(repo_root)
82            .map(|d| d.join("CASCADE_SYNC_STATE").exists())
83            .unwrap_or(false)
84    }
85}