@@ -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+
3157impl 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
3864impl 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 \n body 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: {}" ,
0 commit comments