git_commitizen/
lib.rs

1use git2::{Repository, Signature};
2use std::error::Error;
3use std::path::Path;
4
5pub fn build_commit_types() -> Vec<(&'static str, &'static str)> {
6    vec![
7        ("feat", "A new feature"),
8        ("fix", "A bug fix"),
9        ("docs", "Documentation only changes"),
10        (
11            "style",
12            "Changes that do not affect the meaning of the code (white-space, formatting, etc.)",
13        ),
14        (
15            "refactor",
16            "A code change that neither fixes a bug nor adds a feature",
17        ),
18        ("perf", "A code change that improves performance"),
19        ("test", "Adding missing tests or correcting existing tests"),
20        ("chore", "Other changes that don't modify src or test files"),
21        ("ci", "Changes to our CI configuration files and scripts"),
22        (
23            "build",
24            "Changes that affect the build system or external dependencies",
25        ),
26        ("revert", "Reverts a previous commit"),
27    ]
28}
29
30pub fn format_commit_types(commit_types: Vec<(&str, &str)>) -> Vec<String> {
31    // Determine the maximum length of commit type strings for proper alignment
32    let max_type_length = commit_types
33        .iter()
34        .map(|(typ, _)| typ.len())
35        .max()
36        .unwrap_or(0);
37
38    commit_types
39        .iter()
40        .map(|(typ, desc)| {
41            // Adjust the width to account for proper spacing
42            format!("{:<width$} - {}", typ, desc, width = max_type_length + 4)
43        })
44        .collect()
45}
46
47pub fn build_commit_message(
48    commit_type: &str,
49    scope: &str,
50    description: &str,
51    body: &str,
52    footer: &str,
53) -> String {
54    let message = format!(
55        "{}{}: {}",
56        commit_type,
57        if scope.is_empty() {
58            String::new()
59        } else {
60            format!("({})", scope)
61        },
62        description
63    );
64
65    let mut full_message = message;
66
67    if !body.is_empty() {
68        full_message.push_str(&format!("\n\n{}", body));
69    }
70
71    if !footer.is_empty() {
72        full_message.push_str(&format!("\n\n{}", footer));
73    }
74
75    full_message
76}
77
78pub fn perform_commit(repo_path: &Path, full_commit_message: &str) -> Result<(), Box<dyn Error>> {
79    let repo = Repository::open(repo_path)?;
80
81    let statuses = repo.statuses(None)?;
82    if statuses.is_empty() {
83        return Err("Nothing to commit, working directory clean".into());
84    }
85
86    let mut index = repo.index()?;
87    let tree_id = index.write_tree()?;
88    let tree = repo.find_tree(tree_id)?;
89
90    let config = repo.config()?;
91    let author_name = config.get_string("user.name")?;
92    let author_email = config.get_string("user.email")?;
93    let sig = Signature::now(&author_name, &author_email)?;
94
95    let head = repo.head()?;
96    let parent_commit = repo.find_commit(head.target().ok_or("Failed to find HEAD target")?)?;
97
98    repo.commit(
99        Some("HEAD"),
100        &sig,
101        &sig,
102        &full_commit_message,
103        &tree,
104        &[&parent_commit],
105    )?;
106
107    Ok(())
108}