Skip to main content

apm/cmd/
assign.rs

1use anyhow::{bail, Result};
2use apm_core::{config::{Config, LocalConfig}, git, ticket, ticket_fmt};
3use chrono::Utc;
4use std::path::Path;
5
6pub fn run(root: &Path, id_arg: &str, username: &str, no_aggressive: bool, force: bool) -> Result<()> {
7    run_inner(root, id_arg, username, no_aggressive, force, None)
8}
9
10pub fn run_inner(root: &Path, id_arg: &str, username: &str, no_aggressive: bool, force: bool, confirm_override: Option<bool>) -> Result<()> {
11    let config = Config::load(root)?;
12    let local = LocalConfig::load(root);
13    apm_core::validate::validate_owner(&config, &local, username)?;
14    let aggressive = config.sync.aggressive && !no_aggressive;
15    let mut tickets = ticket::load_all_from_git(root, &config.tickets.dir)?;
16    let id = ticket::resolve_id_in_slice(&tickets, id_arg)?;
17
18    if aggressive {
19        let branches = git::ticket_branches(root).unwrap_or_default();
20        if let Some(b) = branches.iter().find(|b| {
21            b.strip_prefix("ticket/")
22                .and_then(|s| s.split('-').next())
23                .map(|bid| bid == id.as_str())
24                .unwrap_or(false)
25        }) {
26            crate::util::fetch_branch_if_aggressive(root, b, aggressive);
27        }
28    }
29
30    let Some(t) = tickets.iter_mut().find(|t| t.frontmatter.id == id) else {
31        bail!("ticket {id:?} not found");
32    };
33
34    if force {
35        let is_terminal = config.workflow.states.iter()
36            .find(|s| s.id == t.frontmatter.state)
37            .map(|s| s.terminal)
38            .unwrap_or(false);
39        if is_terminal {
40            bail!("cannot change owner of a closed ticket");
41        }
42        if let Some(current_owner) = &t.frontmatter.owner.clone() {
43            let confirmed = match confirm_override {
44                Some(b) => b,
45                None => crate::util::prompt_yes_no(&format!(
46                    "Ticket {id} is currently owned by {current_owner}. Reassign to {username}? [y/N] "
47                ))?
48            };
49            if !confirmed {
50                println!("aborted");
51                return Ok(());
52            }
53        }
54    } else {
55        ticket::check_owner(root, t)?;
56    }
57
58    ticket::set_field(&mut t.frontmatter, "owner", username)?;
59    t.frontmatter.updated_at = Some(Utc::now());
60
61    let content = t.serialize()?;
62    let rel_path = format!(
63        "{}/{}",
64        config.tickets.dir.to_string_lossy(),
65        t.path.file_name().unwrap().to_string_lossy()
66    );
67    let branch = t
68        .frontmatter
69        .branch
70        .clone()
71        .or_else(|| ticket_fmt::branch_name_from_path(&t.path))
72        .unwrap_or_else(|| format!("ticket/{id}"));
73
74    let commit_msg = if username == "-" {
75        format!("ticket({id}): assign owner = -")
76    } else {
77        format!("ticket({id}): assign owner = {username}")
78    };
79
80    git::commit_to_branch(root, &branch, &rel_path, &content, &commit_msg)?;
81
82    if aggressive {
83        if let Err(e) = git::push_branch(root, &branch) {
84            eprintln!("warning: push failed: {e:#}");
85        }
86    }
87
88    if username == "-" {
89        println!("{id}: owner cleared");
90    } else {
91        println!("{id}: owner = {username}");
92    }
93    Ok(())
94}