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::instruction_presets::PresetType;
10use crate::tui::run_tui_commit;
11use crate::ui;
12
13use anyhow::{Context, Result};
14use std::sync::Arc;
15
16#[allow(clippy::fn_params_excessive_bools)]
17#[allow(clippy::too_many_lines)]
18pub async fn handle_message_command(
19 common: CommonParams,
20 auto_commit: bool,
21 use_emoji: bool,
22 print: bool,
23 verify: bool,
24 dry_run: bool,
25 repository_url: Option<String>,
26) -> Result<()> {
27 if !common.is_valid_preset_for_type(PresetType::Commit) {
29 ui::print_warning(
30 "The specified preset may not be suitable for commit messages. Consider using a commit or general preset instead.",
31 );
32 ui::print_info("Run 'git presets' to see available presets for commits.");
33 }
34
35 let mut config = Config::load()?;
36 common.apply_to_config(&mut config)?;
37
38 let service = create_commit_service(
40 &common,
41 repository_url,
42 &config,
43 use_emoji && config.use_emoji,
44 verify,
45 ).map_err(|e| {
46 ui::print_error(&format!("Error: {e}"));
47 ui::print_info("\nPlease ensure the following:");
48 ui::print_info("1. Git is installed and accessible from the command line.");
49 ui::print_info(
50 "2. You are running this command from within a Git repository or provide a repository URL with --repo.",
51 );
52 e
53 })?;
54
55 let git_info = service.get_git_info().await?;
56
57 if git_info.staged_files.is_empty() {
58 ui::print_warning(
59 "No staged changes. Please stage your changes before generating a commit message.",
60 );
61 ui::print_info("You can stage changes using 'git add <file>' or 'git add .'");
62 return Ok(());
63 }
64
65 if let Err(e) = service.pre_commit() {
67 ui::print_error(&format!("Pre-commit failed: {e}"));
68 return Err(e);
69 }
70
71 let effective_instructions = common
72 .instructions
73 .unwrap_or_else(|| config.instructions.clone());
74 let preset_str = common.preset.as_deref().unwrap_or("");
75
76 let spinner = ui::create_spinner("");
78 let random_message = messages::get_waiting_message();
79 spinner.set_message(random_message.text.clone());
80
81 let initial_message = if dry_run {
83 types::GeneratedMessage {
84 emoji: Some("🔧".to_string()),
85 title: "Fix bug in UI rendering".to_string(),
86 message: "Updated the layout to properly handle dynamic constraints and improve user experience.".to_string(),
87 }
88 } else {
89 service
90 .generate_message(preset_str, &effective_instructions)
91 .await?
92 };
93
94 spinner.finish_and_clear();
96
97 if print {
98 println!("{}", format_commit_message(&initial_message));
99 return Ok(());
100 }
101
102 if auto_commit {
103 if service.is_remote_repository() {
105 ui::print_error(
106 "Cannot automatically commit to a remote repository. Use --print instead.",
107 );
108 return Err(anyhow::anyhow!(
109 "Auto-commit not supported for remote repositories"
110 ));
111 }
112
113 match service.perform_commit(&format_commit_message(&initial_message)) {
114 Ok(result) => {
115 let output =
116 format_commit_result(&result, &format_commit_message(&initial_message));
117 println!("{output}");
118 }
119 Err(e) => {
120 eprintln!("Failed to commit: {e}");
121 return Err(e);
122 }
123 }
124 return Ok(());
125 }
126
127 if service.is_remote_repository() {
129 ui::print_warning(
130 "Interactive commit not available for remote repositories. Using print mode instead.",
131 );
132 println!("{}", format_commit_message(&initial_message));
133 return Ok(());
134 }
135
136 run_tui_commit(
137 vec![initial_message],
138 effective_instructions,
139 String::from(preset_str),
140 git_info.user_name,
141 git_info.user_email,
142 service,
143 )
144 .await?;
145
146 Ok(())
147}
148
149pub async fn handle_pr_command(
151 common: CommonParams,
152 _print: bool,
153 repository_url: Option<String>,
154 from: Option<String>,
155 to: Option<String>,
156) -> Result<()> {
157 if !common.is_valid_preset_for_type(PresetType::Review)
159 && !common.is_valid_preset_for_type(PresetType::Both)
160 {
161 ui::print_warning(
162 "The specified preset may not be suitable for PR descriptions. Consider using a review or general preset instead.",
163 );
164 ui::print_info("Run 'git presets' to see available presets for PRs.");
165 }
166
167 validate_pr_parameters(from.as_ref(), to.as_ref());
169
170 let mut config = Config::load()?;
171 common.apply_to_config(&mut config)?;
172
173 let service = setup_pr_service(&common, repository_url, &config)?;
175
176 let pr_description = generate_pr_based_on_parameters(service, common, config, from, to).await?;
178
179 println!("{}", format_pull_request(&pr_description));
181
182 Ok(())
183}
184
185fn validate_pr_parameters(_from: Option<&String>, _to: Option<&String>) {
187 }
196
197fn setup_pr_service(
199 common: &CommonParams,
200 repository_url: Option<String>,
201 config: &Config,
202) -> Result<Arc<CommitService>> {
203 create_commit_service(
205 common,
206 repository_url,
207 config,
208 config.use_emoji, false, )
211}
212
213async fn generate_pr_based_on_parameters(
215 service: Arc<CommitService>,
216 common: CommonParams,
217 config: Config,
218 from: Option<String>,
219 to: Option<String>,
220) -> Result<super::types::GeneratedPullRequest> {
221 let effective_instructions = common
222 .instructions
223 .unwrap_or_else(|| config.instructions.clone());
224 let preset_str = common.preset.as_deref().unwrap_or("");
225
226 let spinner = ui::create_spinner("");
228 let random_message = messages::get_waiting_message();
229 spinner.set_message(format!(
230 "{} - Generating PR description",
231 random_message.text
232 ));
233
234 let pr_description = match (from, to) {
235 (Some(from_ref), Some(to_ref)) => {
236 handle_from_and_to_parameters(
237 service,
238 preset_str,
239 &effective_instructions,
240 from_ref,
241 to_ref,
242 random_message,
243 )
244 .await?
245 }
246 (None, Some(to_ref)) => {
247 handle_to_only_parameter(
248 service,
249 preset_str,
250 &effective_instructions,
251 to_ref,
252 random_message,
253 )
254 .await?
255 }
256 (Some(from_ref), None) => {
257 handle_from_only_parameter(
258 service,
259 preset_str,
260 &effective_instructions,
261 from_ref,
262 random_message,
263 )
264 .await?
265 }
266 (None, None) => {
267 handle_no_parameters(service, preset_str, &effective_instructions, random_message)
268 .await?
269 }
270 };
271
272 spinner.finish_and_clear();
274
275 Ok(pr_description)
276}
277
278async fn handle_from_and_to_parameters(
280 service: Arc<CommitService>,
281 preset_str: &str,
282 effective_instructions: &str,
283 from_ref: String,
284 to_ref: String,
285 random_message: &messages::ColoredMessage,
286) -> Result<super::types::GeneratedPullRequest> {
287 if from_ref == to_ref {
289 let spinner = ui::create_spinner("");
290 spinner.set_message(format!(
291 "{} - Analyzing single commit: {}",
292 random_message.text, from_ref
293 ));
294
295 service
296 .generate_pr_for_commit_range(
297 preset_str,
298 effective_instructions,
299 &format!("{from_ref}^"), &from_ref, )
302 .await
303 } else if is_likely_commit_hash_or_commitish(&from_ref)
304 || is_likely_commit_hash_or_commitish(&to_ref)
305 {
306 let spinner = ui::create_spinner("");
309 spinner.set_message(format!(
310 "{} - Analyzing commit range: {}..{}",
311 random_message.text, from_ref, to_ref
312 ));
313
314 service
315 .generate_pr_for_commit_range(preset_str, effective_instructions, &from_ref, &to_ref)
316 .await
317 } else {
318 let spinner = ui::create_spinner("");
320 spinner.set_message(format!(
321 "{} - Comparing branches: {} -> {}",
322 random_message.text, from_ref, to_ref
323 ));
324
325 service
326 .generate_pr_for_branch_diff(preset_str, effective_instructions, &from_ref, &to_ref)
327 .await
328 }
329}
330
331async fn handle_to_only_parameter(
333 service: Arc<CommitService>,
334 preset_str: &str,
335 effective_instructions: &str,
336 to_ref: String,
337 random_message: &messages::ColoredMessage,
338) -> Result<super::types::GeneratedPullRequest> {
339 let spinner = ui::create_spinner("");
340
341 if is_likely_commit_hash(&to_ref) {
343 spinner.set_message(format!(
345 "{} - Analyzing single commit: {}",
346 random_message.text, to_ref
347 ));
348
349 service
350 .generate_pr_for_commit_range(
351 preset_str,
352 effective_instructions,
353 &format!("{to_ref}^"), &to_ref, )
356 .await
357 } else if is_commitish_syntax(&to_ref) {
358 spinner.set_message(format!(
360 "{} - Analyzing single commit: {}",
361 random_message.text, to_ref
362 ));
363
364 service
365 .generate_pr_for_commit_range(
366 preset_str,
367 effective_instructions,
368 &format!("{to_ref}^"), &to_ref, )
371 .await
372 } else {
373 spinner.set_message(format!(
375 "{} - Comparing main -> {}",
376 random_message.text, to_ref
377 ));
378
379 service
380 .generate_pr_for_branch_diff(preset_str, effective_instructions, "main", &to_ref)
381 .await
382 }
383}
384
385async fn handle_from_only_parameter(
387 service: Arc<CommitService>,
388 preset_str: &str,
389 effective_instructions: &str,
390 from_ref: String,
391 random_message: &messages::ColoredMessage,
392) -> Result<super::types::GeneratedPullRequest> {
393 let spinner = ui::create_spinner("");
394
395 if is_likely_commit_hash(&from_ref) {
397 spinner.set_message(format!(
399 "{} - Analyzing single commit: {}",
400 random_message.text, from_ref
401 ));
402
403 service
404 .generate_pr_for_commit_range(
405 preset_str,
406 effective_instructions,
407 &format!("{from_ref}^"), &from_ref, )
410 .await
411 } else if is_commitish_syntax(&from_ref) {
412 spinner.set_message(format!(
414 "{} - Analyzing range: {}..HEAD",
415 random_message.text, from_ref
416 ));
417
418 service
419 .generate_pr_for_commit_range(preset_str, effective_instructions, &from_ref, "HEAD")
420 .await
421 } else {
422 spinner.set_message(format!(
424 "{} - Analyzing range: {}..HEAD",
425 random_message.text, from_ref
426 ));
427
428 service
429 .generate_pr_for_commit_range(preset_str, effective_instructions, &from_ref, "HEAD")
430 .await
431 }
432}
433
434async fn handle_no_parameters(
436 service: Arc<CommitService>,
437 preset_str: &str,
438 effective_instructions: &str,
439 random_message: &messages::ColoredMessage,
440) -> Result<super::types::GeneratedPullRequest> {
441 let spinner = ui::create_spinner("");
443 spinner.set_message(format!("{} - Comparing main -> HEAD", random_message.text));
444
445 service
446 .generate_pr_for_branch_diff(preset_str, effective_instructions, "main", "HEAD")
447 .await
448}
449
450fn is_likely_commit_hash_or_commitish(reference: &str) -> bool {
452 if reference.len() >= 7 && reference.chars().all(|c| c.is_ascii_hexdigit()) {
454 return true;
455 }
456
457 is_commitish_syntax(reference)
459}
460
461fn is_commitish_syntax(reference: &str) -> bool {
463 reference.contains('~') || reference.contains('^') || reference.starts_with('@')
466}
467
468fn is_likely_commit_hash(reference: &str) -> bool {
470 reference.len() >= 7 && reference.chars().all(|c| c.is_ascii_hexdigit())
471}
472
473fn create_commit_service(
475 common: &CommonParams,
476 repository_url: Option<String>,
477 config: &Config,
478 use_emoji: bool,
479 verify: bool,
480) -> Result<Arc<CommitService>> {
481 let repo_url = repository_url.or(common.repository_url.clone());
483
484 let git_repo = GitRepo::new_from_url(repo_url).context("Failed to create GitRepo")?;
486
487 let repo_path = git_repo.repo_path().clone();
488 let provider_name = &config.default_provider;
489
490 let service = Arc::new(
491 CommitService::new(
492 config.clone(),
493 &repo_path,
494 provider_name,
495 use_emoji,
496 verify,
497 git_repo,
498 )
499 .context("Failed to create CommitService")?,
500 );
501
502 service
504 .check_environment()
505 .context("Environment check failed")?;
506
507 Ok(service)
508}