use std::process::Command;
use anyhow::{Result, anyhow};
use crate::services::git::commits;
use crate::services::storage::config_storage::ConfigStorage;
use crate::utils::project_paths::ProjectPaths;
use crate::services::storage::task_storage::ListStorage;
use crate::services::storage::history_storage::HistoryStorage;
use crate::models::common::Version;
pub fn cmd_semver(
version_str: String,
dry_run: bool,
summary: bool,
auto: bool,
) -> Result<()> {
let paths = ProjectPaths::get_paths()?;
let mut storage = ListStorage::new(&paths.todo_file)?;
let mut history = HistoryStorage::new(&paths.history_file)?;
let config_storage = ConfigStorage::new(&paths.config_file)?;
let config = config_storage.get_config();
let version = Version::parse(&version_str)?;
storage.set_project_version(version.clone())?;
println!("Set project version to '{}'", &version);
let unversioned: Vec<_> = storage
.tasks()
.iter()
.filter(|t| t.completed && t.completed_at_version.is_none())
.collect();
if unversioned.is_empty() {
println!("Nothing to do: No completed tasks without a version.");
return Ok(());
}
if dry_run {
println!(
"Would assign version {} to {} task(s):",
version,
unversioned.len()
);
for task in &unversioned {
println!(" [x] {}", task.description);
}
return Ok(());
}
let task_refs: Vec<&crate::models::tasks::Task> = unversioned.iter().copied().collect();
history.record_all(&task_refs)?;
let count = storage.assign_version_to_completed(version.clone())?;
history.assign_version(&version)?;
println!("Assigned version {} to {} task(s)", version, count);
if summary {
println!();
println!("Tasks in {}:", version);
for entry in history.entries_for_version(&version) {
println!(" • {}", entry.change.description);
}
}
if auto || config.preferences.auto_commit_todo {
commits::commit_tally_files("update TODO: set semver")?;
}
Ok(())
}
pub fn cmd_tag(
version_str: String,
message: Option<String>,
dry_run: bool,
summary: bool,
auto: bool,
) -> Result<()> {
let paths = ProjectPaths::get_paths()?;
let tag_name = if version_str.starts_with('v') {
version_str.clone()
} else {
format!("v{}", version_str)
};
cmd_semver(version_str.clone(), dry_run, summary, auto)?;
let msg = message.unwrap_or_else(|| format!("Release {}", tag_name));
if dry_run {
println!();
println!("Would commit TODO.md and .tally/history.json");
println!("Would create git tag: {} — {}", tag_name, msg);
return Ok(());
}
let output = Command::new("git")
.args(["tag", "-a", &tag_name, "-m", &msg])
.current_dir(&paths.root)
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to create git tag: {}",
String::from_utf8_lossy(&output.stderr)
));
}
println!("✓ Created git tag: {}", tag_name);
Ok(())
}