Skip to content

Commit 2f5d9bc

Browse files
committed
refactor: restructure git module with message abstraction 🔄
- rename git.rs to repository.rs for better semantic clarity - create GitMessage struct to encapsulate commit message logic - extract Author struct for cleaner user data handling - update commit method to accept GitMessage instead of raw strings - integrate table display with structured message formatting Signed-off-by: mingcheng <[email protected]>
1 parent 806dbb9 commit 2f5d9bc

File tree

4 files changed

+112
-53
lines changed

4 files changed

+112
-53
lines changed

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@
1313
*/
1414

1515
pub mod cli;
16-
pub mod git;
16+
// pub mod git;
1717
pub mod openai;
18+
19+
pub mod message;
20+
pub mod repository;

src/main.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
* Last Modified: 2025-09-26 15:45:37
1313
*/
1414

15-
use aigitcommit::cli::Cli;
16-
use aigitcommit::git::Git;
15+
use aigitcommit::cli::{print_table, Cli};
16+
use aigitcommit::message::GitMessage;
1717
use aigitcommit::openai;
1818
use aigitcommit::openai::OpenAI;
19+
use aigitcommit::repository::Git;
1920
use arboard::Clipboard;
2021
use async_openai::error::OpenAIError;
2122
use async_openai::types::{
@@ -135,20 +136,28 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
135136
}
136137
};
137138

139+
let (title, content) = result.split_once("\n\n").unwrap();
140+
138141
// Detect auto signoff from environment variable
139142
let need_signoff = cli.signoff
140143
|| env::var("GIT_AUTO_SIGNOFF")
141144
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
142145
.unwrap_or(false);
143146

147+
let message: GitMessage = GitMessage::new(&repository, title, content, need_signoff)?;
148+
144149
// Write the commit message to stdout
145150
trace!("write to stdout, and finish the process");
146-
writeln!(std::io::stdout(), "{result}")?;
151+
if cli.print_table {
152+
print_table(&message.title, &message.content);
153+
} else {
154+
writeln!(std::io::stdout(), "{}", message)?;
155+
}
147156

148157
// Copy the commit message to clipboard if the --copy option is enabled
149158
if cli.copy {
150159
let mut clipboard = Clipboard::new()?;
151-
clipboard.set_text(&result)?;
160+
clipboard.set_text(&message.to_string())?;
152161
writeln!(
153162
std::io::stdout(),
154163
"the commit message has been copied to clipboard."
@@ -165,7 +174,7 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
165174

166175
// Prompt the user for confirmation if --yes option is not enabled
167176
if cli.yes || confirm.interact()? {
168-
match repository.commit(&result, need_signoff) {
177+
match repository.commit(&message) {
169178
Ok(_) => {
170179
writeln!(std::io::stdout(), "commit successful!")?;
171180
}

src/message.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*!
2+
* Copyright (c) 2025 Hangzhou Guanwaii Technology Co,.Ltd.
3+
*
4+
* This source code is licensed under the MIT License,
5+
* which is located in the LICENSE file in the source tree's root directory.
6+
*
7+
* File: message.rs
8+
* Author: mingcheng <[email protected]>
9+
* File Created: 2025-10-16 15:06:58
10+
*
11+
* Modified By: mingcheng <[email protected]>
12+
* Last Modified: 2025-10-16 15:25:14
13+
*/
14+
15+
use std::{error::Error, fmt::Display};
16+
17+
use log::trace;
18+
19+
use crate::repository::Git;
20+
21+
pub struct GitMessage {
22+
pub title: String,
23+
pub content: String,
24+
}
25+
26+
impl Display for GitMessage {
27+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28+
self.to_string().fmt(f)
29+
}
30+
}
31+
32+
impl GitMessage {
33+
pub fn new(
34+
repository: &Git,
35+
title: &str,
36+
content: &str,
37+
signoff: bool,
38+
) -> Result<Self, Box<dyn Error>> {
39+
if title.is_empty() || content.is_empty() {
40+
return Err("the commit message is invalid".into());
41+
}
42+
43+
let mut content = content.trim().to_string();
44+
// If the --signoff option is enabled, add signoff to the commit message
45+
if signoff {
46+
trace!("signoff option is enabled, will add signoff to the commit message");
47+
48+
let author = repository.get_author()?;
49+
// Add signoff to the commit message
50+
let _m = format!("\n\nSigned-off-by: {} <{}>", author.name, author.email);
51+
content.push_str(&_m);
52+
}
53+
54+
Ok(Self {
55+
title: title.trim().to_string(),
56+
content,
57+
})
58+
}
59+
60+
pub fn is_empty(&self) -> bool {
61+
self.title.is_empty() && self.content.is_empty()
62+
}
63+
64+
pub fn to_string(&self) -> String {
65+
format!("{}\n\n{}", self.title, self.content)
66+
}
67+
}

src/git.rs renamed to src/repository.rs

Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
1-
/*
1+
/*!
22
* Copyright (c) 2025 Hangzhou Guanwaii Technology Co,.Ltd.
33
*
44
* This source code is licensed under the MIT License,
55
* which is located in the LICENSE file in the source tree's root directory.
66
*
7-
* File: git.rs
8-
* Author: mingcheng ([email protected])
9-
* File Created: 2025-03-01 21:55:54
7+
* File: repository.rs
8+
* Author: mingcheng <[email protected]>
9+
* File Created: 2025-10-16 15:07:05
1010
*
11-
* Modified By: mingcheng ([email protected])
12-
* Last Modified: 2025-09-26 14:43:17
11+
* Modified By: mingcheng <[email protected]>
12+
* Last Modified: 2025-10-16 15:28:33
1313
*/
1414

1515
use git2::{Repository, RepositoryOpenFlags, Signature, StatusOptions};
1616
use log::trace;
1717
use std::error::Error;
18+
use std::fmt::{Display, Formatter};
1819
use std::path::Path;
1920

21+
use crate::message::GitMessage;
22+
23+
pub struct Author {
24+
pub name: String,
25+
pub email: String,
26+
}
2027
pub struct Git {
2128
repository: Repository,
2229
}
2330

31+
impl Display for Git {
32+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
33+
write!(f, "Git repository at {}", self.repository.path().display())
34+
}
35+
}
36+
2437
impl Git {
2538
pub fn new(path: &str) -> Result<Git, Box<dyn Error>> {
2639
trace!("opening repository at {path}");
@@ -37,10 +50,10 @@ impl Git {
3750
}
3851

3952
/// Commit the changes in the repository
40-
pub fn commit(&self, message: &str, need_signoff: bool) -> Result<(), Box<dyn Error>> {
53+
pub fn commit(&self, message: &GitMessage) -> Result<(), Box<dyn Error>> {
4154
// Get the current index (staged changes)
4255

43-
let mut message = message.to_string();
56+
let message = message.to_string();
4457
let mut index = self.repository.index()?;
4558

4659
// Write the index to the repository
@@ -51,20 +64,10 @@ impl Git {
5164
let head = self.repository.head()?.peel_to_commit()?;
5265

5366
// Create a new commit
54-
let author_name = self.get_author_name()?;
55-
let author_email = self.get_author_email()?;
67+
let author = self.get_author()?;
5668

5769
// Create a signature for the author and committer
58-
let signature = Signature::now(&author_name, &author_email)?;
59-
60-
// If the --signoff option is enabled, add signoff to the commit message
61-
if need_signoff {
62-
trace!("signoff option is enabled, will add signoff to the commit message");
63-
64-
// Add signoff to the commit message
65-
let signoff = format!("\n\nSigned-off-by: {author_name} <{author_email}>");
66-
message.push_str(&signoff);
67-
}
70+
let signature = Signature::now(&author.name, &author.email)?;
6871

6972
match self.repository.commit(
7073
Some("HEAD"),
@@ -86,40 +89,17 @@ impl Git {
8689
}
8790

8891
/// Get the author email and name from the repository configuration
89-
pub fn get_author_email(&self) -> Result<String, Box<dyn Error>> {
92+
pub fn get_author(&self) -> Result<Author, Box<dyn Error>> {
9093
// Get the configuration of the repository
9194
let config = self.repository.config()?;
9295

9396
// Get the user email from the configuration
94-
match config.get_string("user.email") {
95-
Ok(email) => {
96-
trace!("get author email: {email} from config `user.email`");
97-
Ok(email)
98-
}
99-
100-
Err(e) => {
101-
trace!("failed to get author email: {e}");
102-
Err(Box::new(e))
103-
}
104-
}
105-
}
106-
107-
pub fn get_author_name(&self) -> Result<String, Box<dyn Error>> {
108-
// Get the configuration of the repository
109-
let config = self.repository.config()?;
97+
let email = config.get_string("user.email")?;
11098

11199
// Get the user name from the configuration
112-
match config.get_string("user.name") {
113-
Ok(name) => {
114-
trace!("get author name: {name} from config `user.name`");
115-
Ok(name)
116-
}
100+
let name = config.get_string("user.name")?;
117101

118-
Err(e) => {
119-
trace!("failed to get author name: {e}");
120-
Err(Box::new(e))
121-
}
122-
}
102+
Ok(Author { name, email })
123103
}
124104

125105
/// Get the diff of the current repository

0 commit comments

Comments
 (0)