1use super::format_commit_result;
2use super::service::CommitService;
3use super::types::{format_commit_message, format_pull_request};
4use crate::common::CommonParams;
5use crate::config::Config;
6use crate::core::messages;
7use crate::features::commit::types;
8use crate::git::GitRepo;
9use crate::tui::run_tui_commit;
10use crate::ui;
11
12use anyhow::{Context, Result};
13use std::sync::Arc;
14
15#[allow(clippy::fn_params_excessive_bools)]
16#[allow(clippy::too_many_lines)]
17pub async fn handle_message_command(
18 common: CommonParams,
19 auto_commit: bool,
20 print: bool,
21 verify: bool,
22 dry_run: bool,
23 repository_url: Option<String>,
24) -> Result<()> {
25 let mut config = Config::load()?;
26 common.apply_to_config(&mut config)?;
27
28 let service = create_commit_service(
30 &common,
31 repository_url,
32 &config,
33 verify,
34 ).map_err(|e| {
35 ui::print_error(&format!("Error: {e}"));
36 ui::print_info("\nPlease ensure the following:");
37 ui::print_info("1. Git is installed and accessible from the command line.");
38 ui::print_info(
39 "2. You are running this command from within a Git repository or provide a repository URL with --repo.",
40 );
41 e
42 })?;
43
44 let git_info = service.get_git_info().await?;
45
46 if git_info.staged_files.is_empty() {
47 ui::print_warning(
48 "No staged changes. Please stage your changes before generating a commit message.",
49 );
50 ui::print_info("You can stage changes using 'git add <file>' or 'git add .'");
51 return Ok(());
52 }
53
54 if let Err(e) = service.pre_commit() {
56 ui::print_error(&format!("Pre-commit failed: {e}"));
57 return Err(e);
58 }
59
60 let effective_instructions = common
61 .instructions
62 .unwrap_or_else(|| config.instructions.clone());
63
64 let spinner = ui::create_spinner("");
66 let random_message = messages::get_waiting_message();
67 spinner.set_message(random_message.text.clone());
68
69 let initial_message = if dry_run {
71 types::GeneratedMessage {
72 emoji: Some("🔧".to_string()),
73 title: "Fix bug in UI rendering".to_string(),
74 message: "Updated the layout to properly handle dynamic constraints and improve user experience.".to_string(),
75 }
76 } else {
77 service.generate_message(&effective_instructions).await?
78 };
79
80 spinner.finish_and_clear();
82
83 if print {
84 println!("{}", format_commit_message(&initial_message));
85 return Ok(());
86 }
87
88 if auto_commit {
89 if service.is_remote_repository() {
91 ui::print_error(
92 "Cannot automatically commit to a remote repository. Use --print instead.",
93 );
94 return Err(anyhow::anyhow!(
95 "Auto-commit not supported for remote repositories"
96 ));
97 }
98
99 match service.perform_commit(&format_commit_message(&initial_message)) {
100 Ok(result) => {
101 let output =
102 format_commit_result(&result, &format_commit_message(&initial_message));
103 println!("{output}");
104 }
105 Err(e) => {
106 eprintln!("Failed to commit: {e}");
107 return Err(e);
108 }
109 }
110 return Ok(());
111 }
112
113 if service.is_remote_repository() {
115 ui::print_warning(
116 "Interactive commit not available for remote repositories. Using print mode instead.",
117 );
118 println!("{}", format_commit_message(&initial_message));
119 return Ok(());
120 }
121
122 run_tui_commit(vec![initial_message], effective_instructions, service).await?;
123
124 Ok(())
125}
126
127pub async fn handle_pr_command(
129 common: CommonParams,
130 _print: bool,
131 repository_url: Option<String>,
132 from: Option<String>,
133 to: Option<String>,
134) -> Result<()> {
135 validate_pr_parameters(from.as_ref(), to.as_ref());
137
138 let mut config = Config::load()?;
139 common.apply_to_config(&mut config)?;
140
141 let service = setup_pr_service(&common, repository_url, &config)?;
143
144 let pr_description = generate_pr_based_on_parameters(service, common, config, from, to).await?;
146
147 println!("{}", format_pull_request(&pr_description));
149
150 Ok(())
151}
152
153fn validate_pr_parameters(_from: Option<&String>, _to: Option<&String>) {
155 }
164
165fn setup_pr_service(
167 common: &CommonParams,
168 repository_url: Option<String>,
169 config: &Config,
170) -> Result<Arc<CommitService>> {
171 create_commit_service(
173 common,
174 repository_url,
175 config,
176 false, )
178}
179
180async fn generate_pr_based_on_parameters(
182 service: Arc<CommitService>,
183 common: CommonParams,
184 config: Config,
185 from: Option<String>,
186 to: Option<String>,
187) -> Result<super::types::GeneratedPullRequest> {
188 let effective_instructions = common
189 .instructions
190 .unwrap_or_else(|| config.instructions.clone());
191
192 let spinner = ui::create_spinner("");
194 let random_message = messages::get_waiting_message();
195 spinner.set_message(format!(
196 "{} - Generating PR description",
197 random_message.text
198 ));
199
200 let pr_description = match (from, to) {
201 (Some(from_ref), Some(to_ref)) => {
202 handle_from_and_to_parameters(
203 service,
204 &effective_instructions,
205 from_ref,
206 to_ref,
207 random_message,
208 )
209 .await?
210 }
211 (None, Some(to_ref)) => {
212 handle_to_only_parameter(service, &effective_instructions, to_ref, random_message)
213 .await?
214 }
215 (Some(from_ref), None) => {
216 handle_from_only_parameter(service, &effective_instructions, from_ref, random_message)
217 .await?
218 }
219 (None, None) => {
220 handle_no_parameters(service, &effective_instructions, random_message).await?
221 }
222 };
223
224 spinner.finish_and_clear();
226
227 Ok(pr_description)
228}
229
230async fn handle_from_and_to_parameters(
232 service: Arc<CommitService>,
233 effective_instructions: &str,
234 from_ref: String,
235 to_ref: String,
236 random_message: &messages::ColoredMessage,
237) -> Result<super::types::GeneratedPullRequest> {
238 if from_ref == to_ref {
240 let spinner = ui::create_spinner("");
241 spinner.set_message(format!(
242 "{} - Analyzing single commit: {}",
243 random_message.text, from_ref
244 ));
245
246 service
247 .generate_pr_for_commit_range(
248 effective_instructions,
249 &format!("{from_ref}^"),
250 &from_ref,
251 )
252 .await
253 } else if is_likely_commit_hash_or_commitish(&from_ref)
254 || is_likely_commit_hash_or_commitish(&to_ref)
255 {
256 let spinner = ui::create_spinner("");
259 spinner.set_message(format!(
260 "{} - Analyzing commit range: {}..{}",
261 random_message.text, from_ref, to_ref
262 ));
263
264 service
265 .generate_pr_for_commit_range(effective_instructions, &from_ref, &to_ref)
266 .await
267 } else {
268 let spinner = ui::create_spinner("");
270 spinner.set_message(format!(
271 "{} - Comparing branches: {} -> {}",
272 random_message.text, from_ref, to_ref
273 ));
274
275 service
276 .generate_pr_for_branch_diff(effective_instructions, &from_ref, &to_ref)
277 .await
278 }
279}
280
281async fn handle_to_only_parameter(
283 service: Arc<CommitService>,
284 effective_instructions: &str,
285 to_ref: String,
286 random_message: &messages::ColoredMessage,
287) -> Result<super::types::GeneratedPullRequest> {
288 let spinner = ui::create_spinner("");
289
290 if is_likely_commit_hash(&to_ref) {
292 spinner.set_message(format!(
294 "{} - Analyzing single commit: {}",
295 random_message.text, to_ref
296 ));
297
298 service
299 .generate_pr_for_commit_range(effective_instructions, &format!("{to_ref}^"), &to_ref)
300 .await
301 } else if is_commitish_syntax(&to_ref) {
302 spinner.set_message(format!(
304 "{} - Analyzing single commit: {}",
305 random_message.text, to_ref
306 ));
307
308 service
309 .generate_pr_for_commit_range(effective_instructions, &format!("{to_ref}^"), &to_ref)
310 .await
311 } else {
312 spinner.set_message(format!(
314 "{} - Comparing main -> {}",
315 random_message.text, to_ref
316 ));
317
318 service
319 .generate_pr_for_branch_diff(effective_instructions, "main", &to_ref)
320 .await
321 }
322}
323
324async fn handle_from_only_parameter(
326 service: Arc<CommitService>,
327 effective_instructions: &str,
328 from_ref: String,
329 random_message: &messages::ColoredMessage,
330) -> Result<super::types::GeneratedPullRequest> {
331 let spinner = ui::create_spinner("");
332
333 if is_likely_commit_hash(&from_ref) {
335 spinner.set_message(format!(
337 "{} - Analyzing single commit: {}",
338 random_message.text, from_ref
339 ));
340
341 service
342 .generate_pr_for_commit_range(
343 effective_instructions,
344 &format!("{from_ref}^"),
345 &from_ref,
346 )
347 .await
348 } else if is_commitish_syntax(&from_ref) {
349 spinner.set_message(format!(
351 "{} - Analyzing range: {}..HEAD",
352 random_message.text, from_ref
353 ));
354
355 service
356 .generate_pr_for_commit_range(effective_instructions, &from_ref, "HEAD")
357 .await
358 } else {
359 spinner.set_message(format!(
361 "{} - Analyzing range: {}..HEAD",
362 random_message.text, from_ref
363 ));
364
365 service
366 .generate_pr_for_commit_range(effective_instructions, &from_ref, "HEAD")
367 .await
368 }
369}
370
371async fn handle_no_parameters(
373 service: Arc<CommitService>,
374 effective_instructions: &str,
375 random_message: &messages::ColoredMessage,
376) -> Result<super::types::GeneratedPullRequest> {
377 let spinner = ui::create_spinner("");
379 spinner.set_message(format!("{} - Comparing main -> HEAD", random_message.text));
380
381 service
382 .generate_pr_for_branch_diff(effective_instructions, "main", "HEAD")
383 .await
384}
385
386fn is_likely_commit_hash_or_commitish(reference: &str) -> bool {
388 if reference.len() >= 7 && reference.chars().all(|c| c.is_ascii_hexdigit()) {
390 return true;
391 }
392
393 is_commitish_syntax(reference)
395}
396
397fn is_commitish_syntax(reference: &str) -> bool {
399 reference.contains('~') || reference.contains('^') || reference.starts_with('@')
402}
403
404fn is_likely_commit_hash(reference: &str) -> bool {
406 reference.len() >= 7 && reference.chars().all(|c| c.is_ascii_hexdigit())
407}
408
409fn create_commit_service(
411 common: &CommonParams,
412 repository_url: Option<String>,
413 config: &Config,
414 verify: bool,
415) -> Result<Arc<CommitService>> {
416 let repo_url = repository_url.or(common.repository_url.clone());
418
419 let git_repo = GitRepo::new_from_url(repo_url).context("Failed to create GitRepo")?;
421
422 let repo_path = git_repo.repo_path().clone();
423 let provider_name = &config.default_provider;
424
425 let service = Arc::new(
426 CommitService::new(config.clone(), &repo_path, provider_name, verify, git_repo)
427 .context("Failed to create CommitService")?,
428 );
429
430 service
432 .check_environment()
433 .context("Environment check failed")?;
434
435 Ok(service)
436}