use crate::git;
use crate::task::Task;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Delivery {
pub sha: Option<String>,
pub hint_stale: bool,
}
pub fn resolve(repo_root: &Path, task: &Task) -> Delivery {
let Ok(main_branch) = git::git_current_branch(repo_root) else {
return Delivery { sha: None, hint_stale: false };
};
let tag = format!("[{}]", task.id);
if let Some(hint) = &task.delivered_in {
if git::git_is_ancestor(repo_root, hint, &main_branch)
&& git::git_commit_subject(repo_root, hint)
.is_some_and(|s| s.contains(&tag))
{
return Delivery {
sha: Some(hint.clone()),
hint_stale: false,
};
}
let resolved = git::git_log_find_subject(repo_root, &main_branch, &tag);
let stale = match (&resolved, hint) {
(Some(sha), h) => sha != h,
(None, _) => true,
};
return Delivery {
sha: resolved,
hint_stale: stale,
};
}
Delivery {
sha: git::git_log_find_subject(repo_root, &main_branch, &tag),
hint_stale: false,
}
}
pub fn describe(repo_root: &Path, sha: &str) -> String {
let short = git::git_short_sha(repo_root, sha).unwrap_or_else(|| sha.to_string());
match git::git_commit_subject(repo_root, sha) {
Some(subj) if !subj.is_empty() => format!("{short} {subj}"),
_ => short,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::task::{NewTaskOpts, Task};
use tempfile::TempDir;
fn empty_task() -> Task {
Task::new(
NewTaskOpts {
title: "t".into(),
..Default::default()
},
"bl-abcd".into(),
)
}
#[test]
fn resolve_returns_empty_when_not_a_git_repo() {
let dir = TempDir::new().unwrap();
let d = resolve(dir.path(), &empty_task());
assert!(d.sha.is_none());
assert!(!d.hint_stale);
}
#[test]
fn describe_falls_back_to_short_sha_when_no_subject() {
let dir = TempDir::new().unwrap();
let out = describe(dir.path(), "deadbeef");
assert_eq!(out, "deadbeef");
}
}