Skip to content

Commit 7038341

Browse files
committed
refactor: introduce GitMessageConfig for better extensibility 🧹
- create dedicated `GitMessageConfig` struct to group construction parameters - simplify `GitMessage::new()` signature by accepting config instead of individual args - add comprehensive doc comments explaining config struct purpose and fields - update all call sites and tests to use new config-based constructor - bump version from 1.9.0 to 1.9.1 Signed-off-by: mingcheng <mingcheng@apache.org>
1 parent 0e6a028 commit 7038341

4 files changed

Lines changed: 47 additions & 21 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ dependencies = [
1919

2020
[[package]]
2121
name = "aigitcommit"
22-
version = "1.9.0"
22+
version = "1.9.1"
2323
dependencies = [
2424
"arboard",
2525
"askama",

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "aigitcommit"
3-
version = "1.9.0"
3+
version = "1.9.1"
44
edition = "2024"
55
description = "A simple git commit message generator by OpenAI compaction model."
66
license-file = "LICENSE"

‎src/git/message.rs‎

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,32 @@ pub struct GitMessage {
2828
pub content: String,
2929
}
3030

31+
/// Configuration used when constructing a [`GitMessage`].
32+
///
33+
/// Grouping the construction parameters into a dedicated config struct keeps
34+
/// [`GitMessage::new`] easy to extend with future options (e.g. trailers,
35+
/// scope, breaking-change markers) without breaking call sites.
36+
#[derive(Debug, Clone, Default)]
37+
pub struct GitMessageConfig {
38+
/// The commit title/subject line (will be trimmed).
39+
pub title: String,
40+
/// The commit body/description (will be trimmed).
41+
pub content: String,
42+
/// Whether to append a `Signed-off-by` trailer.
43+
pub signoff: bool,
44+
}
45+
46+
impl GitMessageConfig {
47+
/// Convenience constructor for the most common fields.
48+
pub fn new(title: impl Into<String>, content: impl Into<String>, signoff: bool) -> Self {
49+
Self {
50+
title: title.into(),
51+
content: content.into(),
52+
signoff,
53+
}
54+
}
55+
}
56+
3157
impl Display for GitMessage {
3258
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3359
// Format as: title\n\ncontent
@@ -36,27 +62,20 @@ impl Display for GitMessage {
3662
}
3763

3864
impl GitMessage {
39-
/// Create a new Git commit message
65+
/// Create a new Git commit message from a [`GitMessageConfig`].
4066
///
4167
/// # Arguments
4268
/// * `repository` - The Git repository (used to get author info for signoff)
43-
/// * `title` - The commit title/subject line (will be trimmed)
44-
/// * `content` - The commit body/description (will be trimmed)
45-
/// * `signoff` - Whether to append a "Signed-off-by" line
69+
/// * `config` - Construction parameters; see [`GitMessageConfig`]
4670
///
4771
/// # Returns
4872
/// * `Ok(GitMessage)` - A valid commit message
4973
/// * `Err` - If title or content is empty after trimming
5074
///
51-
pub fn new(
52-
repository: &Repository,
53-
title: &str,
54-
content: &str,
55-
signoff: bool,
56-
) -> Result<Self, Box<dyn Error>> {
75+
pub fn new(repository: &Repository, config: GitMessageConfig) -> Result<Self, Box<dyn Error>> {
5776
// Trim inputs first to check actual content
58-
let title_trimmed = title.trim();
59-
let content_trimmed = content.trim();
77+
let title_trimmed = config.title.trim();
78+
let content_trimmed = config.content.trim();
6079

6180
// Validate both title and content are non-empty
6281
if title_trimmed.is_empty() {
@@ -69,7 +88,7 @@ impl GitMessage {
6988
let mut final_content = content_trimmed.to_string();
7089

7190
// Append signoff line if requested
72-
if signoff {
91+
if config.signoff {
7392
trace!("adding Signed-off-by line to commit message");
7493
let author = repository.get_author()?;
7594
// Writing into the existing String avoids the intermediate alloc
@@ -104,21 +123,25 @@ mod tests {
104123
#[test]
105124
fn rejects_empty_title() {
106125
let Some(repo) = setup() else { return };
107-
let err = GitMessage::new(&repo, " ", "body", false).unwrap_err();
126+
let err = GitMessage::new(&repo, GitMessageConfig::new(" ", "body", false)).unwrap_err();
108127
assert!(err.to_string().contains("title"));
109128
}
110129

111130
#[test]
112131
fn rejects_empty_content() {
113132
let Some(repo) = setup() else { return };
114-
let err = GitMessage::new(&repo, "title", " ", false).unwrap_err();
133+
let err = GitMessage::new(&repo, GitMessageConfig::new("title", " ", false)).unwrap_err();
115134
assert!(err.to_string().contains("content"));
116135
}
117136

118137
#[test]
119138
fn trims_inputs_and_formats_display() {
120139
let Some(repo) = setup() else { return };
121-
let msg = GitMessage::new(&repo, " feat: x ", " body line ", false).unwrap();
140+
let msg = GitMessage::new(
141+
&repo,
142+
GitMessageConfig::new(" feat: x ", " body line ", false),
143+
)
144+
.unwrap();
122145
assert_eq!(msg.title, "feat: x");
123146
assert_eq!(msg.content, "body line");
124147
assert_eq!(format!("{msg}"), "feat: x\n\nbody line");
@@ -127,7 +150,7 @@ mod tests {
127150
#[test]
128151
fn appends_signoff_line_when_requested() {
129152
let Some(repo) = setup() else { return };
130-
let msg = GitMessage::new(&repo, "feat: x", "body", true).unwrap();
153+
let msg = GitMessage::new(&repo, GitMessageConfig::new("feat: x", "body", true)).unwrap();
131154
assert!(
132155
msg.content.contains("Signed-off-by:"),
133156
"signoff line missing: {}",

‎src/main.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use aigitcommit::built_info::{PKG_NAME, PKG_VERSION};
1515
use aigitcommit::cache::Cache;
1616
use aigitcommit::cli::{Cli, Command};
17-
use aigitcommit::git::message::GitMessage;
17+
use aigitcommit::git::message::{GitMessage, GitMessageConfig};
1818
use aigitcommit::git::repository::Repository;
1919
use aigitcommit::openai::OpenAI;
2020
use arboard::Clipboard;
@@ -102,7 +102,10 @@ async fn main() -> utils::Result<()> {
102102
.ok_or("Invalid response format: expected title and content separated by double newline")?;
103103

104104
let need_signoff = should_signoff(&repository, cli.signoff);
105-
let message = GitMessage::new(&repository, title, content, need_signoff)?;
105+
let message = GitMessage::new(
106+
&repository,
107+
GitMessageConfig::new(title, content, need_signoff),
108+
)?;
106109

107110
OutputFormat::detect(cli.json, cli.no_table).write(&message)?;
108111

0 commit comments

Comments
 (0)