use std::path::{Path, PathBuf};
use anyhow::{bail, Result};
use crate::core;
use crate::git;
pub enum PublishOutcome {
Published,
LocalOnly,
Retried,
}
pub struct GitTransaction {
pub repo: PathBuf,
pub dir: PathBuf,
pub remote: bool,
}
impl GitTransaction {
pub fn new(dir: &Path) -> Result<Self> {
let repo = git::repo_root(dir)?;
let has_remote = git::has_remote(&repo).unwrap_or(false);
let pcfg = crate::config::ProjectConfig::load(dir);
let remote = has_remote && pcfg.push_enabled;
if remote {
git::fetch(&repo)?;
}
Ok(Self {
repo,
dir: dir.to_path_buf(),
remote,
})
}
pub fn scan_names(&self) -> Vec<String> {
let mut names = local_ticket_filenames(&self.dir);
if self.remote {
let remote_names = git::remote_ticket_names(&self.repo);
for rn in remote_names {
if !names.contains(&rn) {
names.push(rn);
}
}
}
names
}
pub fn next_id(names: &[String]) -> (String, usize) {
let next = core::max_id(names) + 1;
let width = core::id_width(names);
let tid = format!("{:0>width$}", next, width = width);
(tid, width)
}
pub fn try_push(&self) -> Result<PublishResult> {
if !self.remote {
return Ok(PublishResult::Done(PublishOutcome::LocalOnly));
}
match git::push(&self.repo)? {
git::PushResult::Success => Ok(PublishResult::Done(PublishOutcome::Published)),
git::PushResult::Failed(stderr) => {
bail!("push failed: {}", stderr);
}
git::PushResult::Rejected => {
git::undo_commit(&self.repo)?;
git::pull_rebase(&self.repo)?;
Ok(PublishResult::NeedsRetry)
}
}
}
pub fn push_retry(&self) -> Result<PublishOutcome> {
match git::push(&self.repo)? {
git::PushResult::Success => Ok(PublishOutcome::Retried),
git::PushResult::Failed(stderr) => {
bail!("push failed on retry: {}", stderr);
}
git::PushResult::Rejected => {
bail!("allocation failed after 2 attempts (push repeatedly rejected)");
}
}
}
}
pub enum PublishResult {
Done(PublishOutcome),
NeedsRetry,
}
fn local_ticket_filenames(dir: &Path) -> Vec<String> {
std::fs::read_dir(dir)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "md"))
.map(|e| e.file_name().to_string_lossy().to_string())
.collect()
}