chant/operations/
archive.rs1use anyhow::{Context, Result};
6use std::path::{Path, PathBuf};
7
8use crate::paths::ARCHIVE_DIR;
9use crate::spec::SpecStatus;
10
11#[derive(Debug, Clone, Default)]
13pub struct ArchiveOptions {
14 pub no_stage: bool,
16 pub allow_non_completed: bool,
18}
19
20fn is_git_repo() -> bool {
22 std::process::Command::new("git")
23 .args(["rev-parse", "--git-dir"])
24 .output()
25 .map(|output| output.status.success())
26 .unwrap_or(false)
27}
28
29fn move_spec_file(src: &PathBuf, dst: &PathBuf, no_stage: bool) -> Result<()> {
31 let use_git = !no_stage && is_git_repo();
32
33 if use_git {
34 let status = std::process::Command::new("git")
36 .args(["mv", &src.to_string_lossy(), &dst.to_string_lossy()])
37 .status()
38 .context("Failed to run git mv")?;
39
40 if !status.success() {
41 anyhow::bail!("git mv failed for {}", src.display());
42 }
43 } else {
44 std::fs::rename(src, dst).context(format!(
46 "Failed to move file from {} to {}",
47 src.display(),
48 dst.display()
49 ))?;
50 }
51
52 Ok(())
53}
54
55pub fn archive_spec(specs_dir: &Path, spec_id: &str, options: &ArchiveOptions) -> Result<PathBuf> {
71 use crate::spec;
72
73 let spec = spec::resolve_spec(specs_dir, spec_id)?;
75
76 if spec.frontmatter.status != SpecStatus::Completed && !options.allow_non_completed {
78 anyhow::bail!(
79 "Spec '{}' must be completed to archive (current: {:?})",
80 spec.id,
81 spec.frontmatter.status
82 );
83 }
84
85 let archive_dir = PathBuf::from(ARCHIVE_DIR);
86
87 let date_part = &spec.id[..10]; let date_dir = archive_dir.join(date_part);
90
91 if !date_dir.exists() {
93 std::fs::create_dir_all(&date_dir)?;
94 }
95
96 let source_path = specs_dir.join(format!("{}.md", spec.id));
97 let dest_path = date_dir.join(format!("{}.md", spec.id));
98
99 move_spec_file(&source_path, &dest_path, options.no_stage)?;
101
102 Ok(dest_path)
103}