diff --git a/.gitignore b/.gitignore index 088ba6b..9aa1577 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3b8b69c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2 @@ +# Placeholder Cargo.lock +# Real lockfile generation failed due to missing dependencies in offline environment. diff --git a/README.md b/README.md index f56e69c..9d09f0f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ You may need to close and reopen your terminal after installation. Alternatively export OPENAI_API_KEY='sk-XXXXXXXX' ``` +`auto-commit` uses the `AUTO_COMMIT_MODEL` environment variable to choose which OpenAI model to use when generating commit messages. If this variable is not set, the tool defaults to `gpt-4.1-nano`. + +```bash +export AUTO_COMMIT_MODEL='gpt-4.1-nano' +``` + Once you have configured your environment, stage some changes by running, for example, `git add .`, and then run `auto-commit`. Of course, `auto-commit` also includes some options, for editing the message before commiting, or just printing the message to the terminal. diff --git a/src/lib.rs b/src/lib.rs index 26c6ab5..400a0fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,11 @@ pub fn truncate_to_n_tokens(text: &str, limit: usize) -> String { text.split_whitespace().take(limit).collect::>().join(" ") } +pub const DEFAULT_MODEL: &str = "gpt-4.1-nano"; + pub fn get_model_from_env() -> String { - std::env::var("AUTO_COMMIT_MODEL").unwrap_or_else(|_| "gpt-4.1-nano".to_string()) + std::env::var("AUTO_COMMIT_MODEL").unwrap_or_else(|_| DEFAULT_MODEL.to_string()) + } #[cfg(test)] @@ -19,7 +22,7 @@ mod tests { #[test] fn test_get_model_from_env_default() { std::env::remove_var("AUTO_COMMIT_MODEL"); - assert_eq!(get_model_from_env(), "gpt-4.1-nano"); + assert_eq!(get_model_from_env(), DEFAULT_MODEL); } #[test] @@ -81,4 +84,5 @@ mod tests { assert_eq!(get_model_from_env(), custom); std::env::remove_var("AUTO_COMMIT_MODEL"); } -} \ No newline at end of file +} + diff --git a/src/main.rs b/src/main.rs index 526d7ff..f01b934 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,8 @@ impl ToString for Commit { } } +const MAX_DIFF_TOKENS: usize = 20_000; + #[tokio::main] async fn main() -> Result<(), ()> { let cli = Cli::parse(); @@ -80,10 +82,19 @@ async fn main() -> Result<(), ()> { .arg("diff") .arg("--staged") .output() - .expect("Couldn't find diff.") + .map_err(|e| { + error!("Failed to get staged diff: {}", e); + () + })? .stdout; - let git_staged_cmd = str::from_utf8(&git_staged_cmd).unwrap(); + let git_staged_cmd = match str::from_utf8(&git_staged_cmd) { + Ok(v) => v, + Err(e) => { + error!("Staged diff output was not valid UTF-8: {}", e); + "" + } + }; if git_staged_cmd.is_empty() { error!("There are no staged files to commit.\nTry running `git add` to stage some files."); @@ -93,10 +104,22 @@ async fn main() -> Result<(), ()> { .arg("rev-parse") .arg("--is-inside-work-tree") .output() - .expect("Failed to check if this is a git repository.") + .map_err(|e| { + error!("Failed to check if this is a git repository: {}", e); + () + })? .stdout; - if str::from_utf8(&is_repo).unwrap().trim() != "true" { + if match str::from_utf8(&is_repo) { + Ok(v) => v.trim() != "true", + Err(e) => { + error!("Git repository check output was not valid UTF-8: {}", e); + true // Treat as not a repo if output is invalid + } + } { + error!("It looks like you are not in a git repository.\nPlease run this command from the root of a git repository, or initialize one using `git init`."); + std::process::exit(1); + } error!("It looks like you are not in a git repository.\nPlease run this command from the root of a git repository, or initialize one using `git init`."); std::process::exit(1); } @@ -108,29 +131,37 @@ async fn main() -> Result<(), ()> { .arg("--name-only") .arg("--staged") .output() - .expect("Couldn't get changed files.") + .map_err(|e| { + error!("Couldn't get changed files: {}", e); + () + })? .stdout; - let files_changed = str::from_utf8(&files_output).map_err(|e| { - error!("Git diff --name-only output is not valid UTF-8: {}. Attempting lossy conversion.", e); - // Decide on error strategy, e.g. propagate with `?` after mapping to `()` - // For a direct replacement that avoids panic but might have imperfect strings: - // String::from_utf8_lossy(&files_output).into_owned() - // Or, to propagate the error if main returns Result: - // return Err(()); // after mapping error to () for main's signature - // For now, let's suggest a pattern that would fit with `?` if error mapped - panic!("Non-UTF8 output from git: {}", e); // Placeholder, better to map and use `?` - }).unwrap_or_else(|_| String::new()); // Fallback to empty or handle error properly + let files_changed = match str::from_utf8(&files_output) { + Ok(v) => v, + Err(e) => { + error!("Changed files output was not valid UTF-8: {}", e); + "" + } + }; let diff_output = Command::new("git") .arg("diff") .arg("--staged") .output() - .expect("Couldn't find diff.") + .map_err(|e| { + error!("Couldn't find diff: {}", e); + () + })? .stdout; - let diff_output = str::from_utf8(&diff_output).unwrap(); + let diff_output = match str::from_utf8(&diff_output) { + Ok(v) => v, + Err(e) => { + error!("Diff output was not valid UTF-8: {}", e); + "" + } + }; let combined = format!("Changed files:\n{}\n\nDiff:\n{}", files_changed, diff_output); - const MAX_DIFF_TOKENS: usize = 20_000; // Or define elsewhere let output = truncate_to_n_tokens(&combined, MAX_DIFF_TOKENS); if !cli.dry_run { @@ -186,7 +217,7 @@ async fn main() -> Result<(), ()> { ChatCompletionRequestMessage { role: Role::System, content: Some( - "You are an experienced programmer who writes great commit messages." + "You are an experienced developer who writes great commit messages." .to_string(), ), ..Default::default()