git_pm/
lib.rs

1use deunicode::deunicode;
2use serde::Deserialize;
3use std::process::Command;
4
5#[derive(Debug, Deserialize)]
6pub struct Config {
7    pub prefixes: Option<Vec<String>>,
8    pub default_prefix: Option<String>,
9}
10
11pub fn load_config() -> (Vec<String>, String) {
12    let mut prefixes = vec![
13        "feature".to_string(),
14        "bugfix".to_string(),
15        "hotfix".to_string(),
16        "release".to_string(),
17        "wip".to_string(),
18        "chore".to_string(),
19        "epic".to_string(),
20        "experimente".to_string(),
21        "docs".to_string(),
22    ];
23    let mut default_prefix = "feature".to_string();
24
25    // Try repo-local .gitpm.toml first, then $HOME/.gitpm.toml
26    let paths = [
27        std::path::PathBuf::from(".gitpm.toml"),
28        dirs::home_dir().unwrap_or_default().join(".gitpm.toml"),
29    ];
30
31    for path in paths.iter() {
32        if path.exists() {
33            if let Ok(contents) = std::fs::read_to_string(path) {
34                if let Ok(config) = toml::from_str::<Config>(&contents) {
35                    if let Some(custom_prefixes) = config.prefixes {
36                        prefixes = custom_prefixes;
37                    }
38                    if let Some(dp) = config.default_prefix {
39                        default_prefix = dp;
40                    }
41                    break;
42                }
43            }
44        }
45    }
46
47    (prefixes, default_prefix)
48}
49
50pub fn sanitize_branch(input: &str) -> String {
51    let mut parts = input.splitn(2, ' ');
52    let task_id_raw = parts.next().unwrap_or("");
53    let rest_raw = parts.next().unwrap_or("");
54
55    // Ensure the task id stays uppercase (robust if user typed it lowercase)
56    let task_id = task_id_raw.to_uppercase();
57
58    // Transliterate to ASCII, then lowercase
59    let normalized = deunicode(rest_raw).to_lowercase();
60
61    // Replace non-alnum with '-', keeping only [a-z0-9-]
62    let mut buf = String::with_capacity(normalized.len());
63    let mut last_dash = false;
64    for ch in normalized.chars() {
65        let c = if ch.is_ascii_alphanumeric() { ch } else { '-' };
66        if c == '-' {
67            if !last_dash {
68                buf.push('-');
69                last_dash = true;
70            }
71        } else {
72            buf.push(c);
73            last_dash = false;
74        }
75    }
76
77    let branch_rest = buf.trim_matches('-');
78
79    if branch_rest.is_empty() {
80        task_id
81    } else {
82        format!("{}-{}", task_id, branch_rest)
83    }
84}
85
86pub fn branch_exists(branch: &str) -> bool {
87    Command::new("git")
88        .args(["rev-parse", "--verify", branch])
89        .output()
90        .map(|o| o.status.success())
91        .unwrap_or(false)
92}