task-track 0.7.0

A JJ workspace-based task and TODO management CLI tool
Documentation
use crate::utils::Result;
use chrono::Utc;
use std::path::Path;

pub fn determine_branch_name(
    branch: Option<&str>,
    ticket_id: Option<&str>,
    task_id: i64,
    todo_index: Option<i64>,
) -> Result<String> {
    match (branch, ticket_id, todo_index) {
        (Some(b), Some(t), _) => Ok(format!("{}/{}", t, b)),
        (Some(b), None, _) => Ok(b.to_string()),
        (None, Some(t), Some(todo)) => Ok(format!("{}-todo-{}", t, todo)),
        (None, None, Some(todo)) => Ok(format!("task-{}-todo-{}", task_id, todo)),
        (None, Some(t), None) => Ok(format!("task/{}", t)),
        (None, None, None) => {
            let timestamp = Utc::now().timestamp();
            Ok(format!("task-{}-{}", task_id, timestamp))
        }
    }
}

pub fn task_bookmark_name(task_id: i64, ticket_id: Option<&str>) -> String {
    if let Some(ticket) = ticket_id {
        format!("task/{}", ticket)
    } else {
        format!("task/task-{}", task_id)
    }
}

pub fn determine_worktree_path(repo_path: &str, branch: &str) -> Result<String> {
    let sanitized_branch = branch.replace(['/', '\\'], "_");
    let worktree_path = Path::new(repo_path).join(sanitized_branch);
    Ok(worktree_path.to_string_lossy().to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn determine_branch_name_with_explicit_branch_and_ticket() {
        let result = determine_branch_name(Some("feature-x"), Some("PROJ-123"), 1, None).unwrap();
        assert_eq!(result, "PROJ-123/feature-x");
    }

    #[test]
    fn determine_branch_name_with_explicit_branch_only() {
        let result = determine_branch_name(Some("feature-y"), None, 1, None).unwrap();
        assert_eq!(result, "feature-y");
    }

    #[test]
    fn determine_branch_name_with_ticket_and_todo() {
        let result = determine_branch_name(None, Some("PROJ-456"), 1, Some(5)).unwrap();
        assert_eq!(result, "PROJ-456-todo-5");
    }

    #[test]
    fn determine_branch_name_with_todo_only() {
        let result = determine_branch_name(None, None, 2, Some(7)).unwrap();
        assert_eq!(result, "task-2-todo-7");
    }

    #[test]
    fn determine_branch_name_base_with_ticket() {
        let result = determine_branch_name(None, Some("PROJ-789"), 3, None).unwrap();
        assert_eq!(result, "task/PROJ-789");
    }

    #[test]
    fn determine_branch_name_base_without_ticket() {
        let result = determine_branch_name(None, None, 4, None).unwrap();
        assert!(result.starts_with("task-4-"));
    }

    #[test]
    fn determine_worktree_path_sanitizes_slashes() {
        let repo_path = if cfg!(windows) {
            "C:\\path\\to\\repo"
        } else {
            "/path/to/repo"
        };
        let result = determine_worktree_path(repo_path, "feature/test").unwrap();
        let expected = Path::new(repo_path)
            .join("feature_test")
            .to_string_lossy()
            .to_string();
        assert_eq!(result, expected);
    }
}